以下方法使用RNGCryptoServiceProvider
返回一串随机字符。返回字符串result
是通过应用chars
从字符串% chars.length
中挑选字符而构建的
GetBytes()
返回的字节数组中的字节值(0-255)。这意味着根据chars
的长度,某些字符可能比其他字符更受青睐。
如何重写该方法,以使chars
中的所有字符都具有相等的被选中机会?
/// <summary>
/// Returns a string of cryptographically sound random characters
/// </summary>
/// <param name="type">Accepted parameter variables are HEX (0-F), hex (0-f),
/// DEC/dec/NUM/num (0-9), ALPHA (A-Z), alpha (a-z), ALPHANUM (A-Z and 0-9),
/// alphanum (a-z and 0-9) and FULL/full (A-Z, a-z and 0-9)</param>
/// <param name="length">The length of the output string</param>
/// <returns>String of cryptographically sound random characters</returns>
private static string Serial(string type, int length)
{
if (length < 1) return "";
string chars;
switch (type)
{
case "HEX":
chars = "0123456789ABCDEF";
break;
case "hex":
chars = "0123456789abcdef";
break;
case "DEC":
case "dec":
case "NUM":
case "num":
chars = "0123456789";
break;
case "ALPHA":
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
break;
case "alpha":
chars = "abcdefghijklmnopqrstuvwxyz";
break;
case "ALPHANUM":
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
break;
case "alphanum":
chars = "abcdefghijklmnopqrstuvwxyz0123456789";
break;
case "FULL":
case "full":
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
break;
default:
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
break;
}
byte[] data = new byte[length];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
StringBuilder result = new StringBuilder(length);
foreach (byte b in data)
{
result.Append(chars[b % chars.Length]);
}
return result.ToString();
}
答案 0 :(得分:2)
Rejection sampling似乎是最实用,最公正的方法,
private const int blockSize = 1024;
private readonly RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
private static IEnumerable<byte> GetCryptoStream()
{
var data = new byte[blockSize];
while (true)
{
crypto.GetBytes(data);
foreach (var b in data) yield return b;
}
}
private static IEnumerable<int> GetCryptoStream(int radix)
{
if (radix <= 0 || radix >= 256) throw new ArgumentException("radix should be > 0 and < 256");
var rem = 256 % radix;
foreach (var b in GetCryptoStream())
{
if (b < rem) continue; // rejection sampling
yield return b % radix;
}
}
private static string Serial(string type, int length)
{
if (length < 1) return "";
string chars;
switch (type)
{
// ...
}
var result = new StringBuilder(length);
foreach (var k in GetCryptoStream(chars.Length).Take(length))
{
result.Append(chars[k]);
}
return result.ToString();
}
测试代码,
[TestCase(3)]
[TestCase(6)]
[TestCase(5)]
[TestCase(75)]
public void TestCrypto(int n)
{
const int m = 1000 * 1000 * 100;
var count = new int[n];
foreach (var k in GetCryptoStream(n).Take(m))
{
count[k] ++;
}
Console.WriteLine($"Expected : {1.0 / n:F4}, Actual :");
for (int i = 0; i < count.Length; i++)
{
Console.WriteLine($"{i} : {count[i] / (double) m:F4}");
}
}
结果为6
Expected : 0.1667, Actual :
0 : 0.1667
1 : 0.1667
2 : 0.1667
3 : 0.1667
4 : 0.1667
5 : 0.1666