随机密码生成器:一个示例






4.67/5 (2投票s)
using System.Security.Cryptography.RNGCryptoServiceProvider
引言
当需要生成需要更高随机性的随机数(更难预测/猜测/有偏差的分布)时,流行的 System.Random 并不是一个好选择。相反,RNGCryptoServiceProvider(基于 文档)实现了加密随机数生成器 (RNG),这是一个推荐的选择。为了比较这两种选择,优点和缺点,以及它们的使用方法,你可以谷歌搜索它们。本文旨在分享我使用需要加密安全的项目的工作经验,而 RNGCryptoServiceProvider 是我的选择。
注释
一个密码可能包含 4 种类型的字符
- 小写字母
- 大写字母
- 数字
- 符号
一些观察
- 字符 'l' 有时会与数字 1 混淆。
- 密码通常需要是 n 个字符的长度。它包含 x 个小写字母,y 个大写字母,z 个数字和 t 个符号字符(其中 x + y + z + t = n)。
计划是
- 生成一个由随机 x 个小写字母组成的字符串
- 生成一个由随机 y 个大写字母组成的字符串
- 生成一个由随机 z 个数字组成的字符串
- 生成一个由随机 t 个符号字符组成的字符串
- 将它们全部加在一起
- 如果需要,打乱它们的顺序
这项工作可以分为两个类:RandomService (步骤 1 - 4) 和 PasswordService (步骤 5 - 6)。
代码
确定可接受的字符集
首先,我们需要上面 4 种类型中每种类型的可能字符集。
与其简单地定义成这样
public static string _LOWERCASEALPHABETICAL = "abcdefghijklmnopqrstuvwxyz";
public static string _UPPERCASEALPHABETICAL = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string _NUMERICAL = "0123456789";
public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";
我们可以创建一个辅助方法,能够过滤掉一些不需要的字符。当我们需要稍后更改不包含哪些字符时,这将很方便
public static string GetCharRange(char begin, char end, string excludingChars = "")
{
var result = string.Empty;
for (char c = begin; c <= end; c++)
{
result += c;
}
if (!string.IsNullOrEmpty(excludingChars))
{
var inclusiveChars = result.Except(excludingChars).ToArray();
result = new string(inclusiveChars);
}
return result;
}
下面的调用将从所有可能的小写字母集合中排除 'i' 和 'l'
public static string _LOWERCASEALPHABETICAL = GetCharRange('a', 'z', "li"); // "abcdefghjkmnopqrstuvwxyz";
public static string _UPPERCASEALPHABETICAL = GetCharRange('A', 'Z'); // "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string _NUMERICAL = GetCharRange('0', '9'); // "0123456789";
public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";
有了以上 4 组可接受的字符,我们需要构建 4 个随机字符串,每个字符串包含一定数量的字符(x、y、z、t)。这些随机字符串中选择的每个字符都对应于该字符在上述 4 个集合中的索引。换句话说,这里的“随机”是随机索引(随机数),在字符数组的索引范围内。为了更清楚,字符串是字符数组。具体来说,这里的随机数是在 [0, 长度 - 1] 范围内的生成的数。
RandomService 类
我们设计这个类来使用 RNGCryptoServiceProvider 生成随机数
public static class RandomService
{
public static readonly RNGCryptoServiceProvider _rngProvider = new RNGCryptoServiceProvider();
public static int NextInt32()
{
var randomBuffer = new byte[4];
_rngProvider.GetBytes(randomBuffer);
return BitConverter.ToInt32(randomBuffer, 0);
}
// Generate a random real number within range [0.0, 1.0]
public static double Next()
{
var buffer = new byte[sizeof(uint)];
_rngProvider.GetBytes(buffer); // Fill the buffer
uint random = BitConverter.ToUInt32(buffer, 0);
return random / (1.0 + uint.MaxValue);
}
// Generate an int within range [min, max - 1] if max > min, and min if min == max
public static int Next(int min, int max)
{
if (min > max) throw new ArgumentException($"Second input ({max}) must be greater than first input ({min}))");
return (int)(min + (max - min) * Next());
}
// Generate an int within range [0, max - 1] if max > 1, and 0 if max == {0, 1}
public static int Next(int max) => Next(0, max);
}
PasswordService 类
现在,我们可以通过使用生成的随机数(来自 RandomService 类)作为随机索引从可接受的字符集中获取字符来生成随机密码。下面的方法 GetRandomString(string, int) 旨在实现我们所讨论的内容
public static class PasswordService
{
public static string _LOWERCASEALPHABETICAL = GetCharRange('a', 'z', "li"); // "abcdefghjkmnopqrstuvwxyz";
public static string _UPPERCASEALPHABETICAL = GetCharRange('A', 'Z'); // "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string _NUMERICAL = GetCharRange('0', '9'); // "0123456789";
public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";
public static string RandomUpperCase(int length) => GetRandomString(_UPPERCASEALPHABETICAL, length);
public static string RandomLowerCase(int length) => GetRandomString(_LOWERCASEALPHABETICAL, length);
public static string RandomNumerical(int length) => GetRandomString(_NUMERICAL, length);
public static string RandomSymbol(int length) => GetRandomString(_SYMBOL, length);
public static string GetCharRange(char begin, char end, string excludingChars = "")
{
... shown above
}
private static string GetRandomString(string possibleChars, int len)
{
var result = string.Empty;
for (var position = 0; position < len; position++)
{
var index = RandomService.Next(possibleChars.Length);
result += possibleChars[index];
}
return result;
}
...
}
最后,我们根据所需的数字 x、y、z 和 t 生成密码
public static class PasswordService
{
...
public static string GenerateRandomPassword(int numOfLowerCase, int numOfUpperCase, int numOfNumeric, int numOfSymbol)
{
string ordered = RandomLowerCase(numOfLowerCase)
+ RandomUpperCase(numOfUpperCase)
+ RandomNumerical(numOfNumeric)
+ RandomSymbol(numOfSymbol);
string shuffled = new string(ordered.OrderBy(n => RandomService.NextInt32()).ToArray());
return shuffled;
}
}
请注意,在没有打乱顺序的情况下,密码的字符按类型(小写、大写、数字或符号)排序。有几种打乱顺序的方法,例如,Fisher-Yates 算法。这个程序使用 LINQ 扩展方法根据随机键对密码字符串的元素进行排序。
如果第一个字符不允许是符号,则有几种解决方法,例如,在打乱顺序后在第一个位置插入一个新的随机字母数字字符。
测试结果
static void GetAndShowPassword(int numOfPass, int lower, int upper, int num, int symbol)
{
WriteLine($"{numOfPass} generated passwords containing: {lower} lower, {upper} upper, {num} numerical, and {symbol} symbol character(s):");
for (int i = 0; i < numOfPass; i++)
{
string pass = PasswordService.GenerateRandomPassword(lower, upper, num, symbol);
WriteLine($"\t{pass}");
}
WriteLine();
}
static void Main(string[] args)
{
GetAndShowPassword(4, lower: 2, upper: 2, num: 2, symbol: 2);
GetAndShowPassword(5, lower: 0, upper: 4, num: 4, symbol: 0);
GetAndShowPassword(6, lower: 0, upper: 4, num: 4, symbol: 2);
ReadKey();
}