Generate random passwords in .NET without modulo bias
Illustrative code, not executed.
Original English publication date: 2022-03-03T09:09:00.007+02:00. Original English article
The archived example links to a Stack Overflow password generator. Its cryptographic byte source is not enough to make every step suitable for passwords: byte % 87 biases character selection, and the later use of System.Random is not a cryptographic replacement. The corrected example below uses RandomNumberGenerator.GetInt32 for every choice. [1]
A byte has 256 possible values. Dividing those into 87 buckets leaves a remainder, so some buckets receive more inputs. GetInt32 uses rejection rather than simple modulo reduction. The original punctuation string also contains 25 characters, matching indexes 62 through 86; that array size is not the problem. [1]
An explicit character policy
This illustrative modern-.NET function produces exactly symbolCount punctuation characters, not the original minimum count. It fills the other positions from letters and digits, then uses a cryptographic Fisher–Yates shuffle. Each alphabet contains distinct characters. The sample length range of 16–128 is an application policy, not a universal password-strength guarantee. It does not promise at least one digit or both letter cases. [1]
using System;
using System.Security.Cryptography;
public static class PasswordExample
{
// Illustrative only; not executed as part of this article.
public static string Generate(int length = 20, int symbolCount = 4)
{
const string LettersAndDigits =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const string Symbols = "!@#$%^&*()_-+=[{]};:>|./?";
if (length < 16 || length > 128)
throw new ArgumentOutOfRangeException(nameof(length));
if (symbolCount < 0 || symbolCount > length)
throw new ArgumentOutOfRangeException(nameof(symbolCount));
char[] result = new char[length];
for (int i = 0; i < length; i++)
{
string alphabet = i < symbolCount ? Symbols : LettersAndDigits;
result[i] = alphabet[RandomNumberGenerator.GetInt32(alphabet.Length)];
}
for (int i = result.Length - 1; i > 0; i--)
{
int j = RandomNumberGenerator.GetInt32(i + 1);
(result[i], result[j]) = (result[j], result[i]);
}
string password = new string(result);
Array.Clear(result, 0, result.Length);
return password; // The returned immutable string cannot be wiped reliably.
}
}Choose a length and alphabet that the destination actually accepts. Do not log or reuse the result. Clearing the temporary character array does not erase the returned string or all runtime copies. This is an unexecuted code example; no password was generated and no account was changed. For older target frameworks, use a reviewed cryptographic rejection-sampling helper rather than substituting System.Random.
Primary references
Original links in historical context
Original links are retained for attribution. They do not endorse the old code; current availability was not verified.