This is a bit tricky to get it to be totally random. I think @stuartd is right in that the way to do it is to get all categories of chars and then random shuffle them at the end. Here's one way to do that. I randomize the count of chars for each category as well although with the 8 char password length and 4 categories that is not a wide range (you should increase your password length). Random shuffle algorithm from this answer.
const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string lower = "abcdefghijklmnopqrstuvwxyz";
const string caractere = "!-_*+&$";
const string number = "0123456789";
static string[] all = new [] { upper,lower,caractere,number };
const int PwdLength = 8;
const int CharCategories = 4;
const int CharCatLimit = PwdLength / CharCategories;
static void Main(string[] _)
{
string[] randomPwds = Enumerable.Range(0, 10).Select(_ => GetRandomPwd()).ToArray();
foreach (var pwd in randomPwds)
{
bool good = pwd.Any(x => upper.Contains(x)) &&
pwd.Any(x => lower.Contains(x)) &&
pwd.Any(x => caractere.Contains(x)) &&
pwd.Any(x => number.Contains(x));
Console.WriteLine($"{pwd} => has all required chars: {good}");
}
}
static string GetRandomPwd()
{
Random rand = new Random();
var password = GetRandCountRanCharsFromStr(upper, CharCatLimit, rand)
.Concat(GetRandCountRanCharsFromStr(lower, CharCatLimit, rand))
.Concat(GetRandCountRanCharsFromStr(caractere, CharCatLimit, rand))
.Concat(GetRandCountRanCharsFromStr(number, CharCatLimit, rand)).ToArray();
if (password.Length < PwdLength)
{
password = password.Concat(GetRandCharsFromStr(all[rand.Next(0, all.Length)], PwdLength - password.Length, rand))
.ToArray();
}
Shuffle(password, rand);
return new string(password);
}
static void Shuffle(char[] source, Random rand)
{
int n = source.Length;
while (n > 1)
{
--n;
int k = rand.Next(n + 1);
var temp = source[k];
source[k] = source[n];
source[n] = temp;
}
}
static IEnumerable<char> GetRandCountRanCharsFromStr(string source, int count, Random rand)
{
int randLimit = rand.Next(1, count);
for (int i = 0; i < randLimit; ++i)
{
yield return source[rand.Next(0, source.Length)];
}
}
static IEnumerable<char> GetRandCharsFromStr(string source, int count, Random rand)
{
for (int i = 0; i < count; ++i)
yield return source[rand.Next(0, source.Length)];
}