C# 使用RNGCryptoServiceProvider生成随机字符串

C# 使用RNGCryptoServiceProvider生成随机字符串,c#,random,C#,Random,我用这段代码生成给定长度的随机字符串 public string RandomString(int length) { const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; StringBuilder res = new StringBuilder(); Random rnd = new Random(); while (0 < lengt

我用这段代码生成给定长度的随机字符串

public string RandomString(int length)
{
    const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    StringBuilder res = new StringBuilder();
    Random rnd = new Random();
    while (0 < length--)
    {
        res.Append(valid[rnd.Next(valid.Length)]);
    }
    return res.ToString();
}
公共字符串RandomString(int-length)
{
常量字符串valid=“abcdefghijklmnopqrstuvxyzabcdefghijklmnopqrstuvxyz1234567890”;
StringBuilder res=新的StringBuilder();
随机rnd=新随机();
而(0<长度--)
{
res.Append(有效[rnd.Next(有效.Length)]);
}
return res.ToString();
}

然而,我读到,
RNGCryptoServiceProvider
Random
类更安全。如何实现此函数的
RNGCryptoServiceProvider
。它应该像此函数一样使用
valid
字符串。

RNGCryptoServiceProvider以字节的形式返回随机数,因此您需要一种方法从中获取更方便的随机数:

public static int GetInt(RNGCryptoServiceProvider rnd, int max) {
  byte[] r = new byte[4];
  int value;
  do {
    rnd.GetBytes(r);
    value = BitConverter.ToInt32(r, 0) & Int32.MaxValue;
  } while (value >= max * (Int32.MaxValue / max));
  return value % max;
}
然后,您可以在方法中使用:

public static string RandomString(int length) {
  const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
  StringBuilder res = new StringBuilder();
  using (RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider()) {
    while (length-- > 0) {
      res.Append(valid[GetInt(rnd, valid.Length)]);
    }
  }
  return res.ToString();
}

(我将该方法设置为静态,因为它不使用任何实例数据。)

由于RNGRandomNumberGenerator只返回字节数组,因此必须这样做:

static string RandomString(int length)
{
    const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    StringBuilder res = new StringBuilder();
    using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
    {
        byte[] uintBuffer = new byte[sizeof(uint)];

        while (length-- > 0)
        {
            rng.GetBytes(uintBuffer);
            uint num = BitConverter.ToUInt32(uintBuffer, 0);
            res.Append(valid[(int)(num % (uint)valid.Length)]);
        }
    }

    return res.ToString();
}
但是请注意,这有一个缺陷,62个有效字符等于595419631038687520880611235991756位(log(62)/log(2)),因此它不会在32位数字(uint)上平均分割

这有什么后果? 因此,随机输出将不均匀。值较低的字符出现的可能性更大(只是很小的一部分,但仍然会发生)

更准确地说,一个有效数组的前4个字符000000 144354999840239435286%的可能性更高


为了避免这种情况,您应该使用将平均分成64的数组长度(考虑在输出上使用,因为您可以将64位与6个字节完全匹配。

您需要使用
RNGCryptoServiceProvider
生成随机
字节
,并仅将有效的字节附加到返回的
字符串中:

const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

static string GetRandomString(int length)
{
    string s = "";
    using (RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider())
    {
        while (s.Length != length)
        {
            byte[] oneByte = new byte[1];
            provider.GetBytes(oneByte);
            char character = (char)oneByte[0];
            if (valid.Contains(character))
            {
                s += character;
            }
        }
    }
    return s;
}
您还可以使用模运算,以避免跳过无效的
字节
值,但每个字符的机会不会均等。

请参阅

我相信我以前已经用一个安全的实现、无偏见和良好的性能回答了这个问题。如果是,请评论

看着Tamir的答案,我认为最好使用模运算,但去掉字节值中不完整的剩余部分。我现在也在写这个答案(可能再次),因为我需要将这个解决方案引用给对等方

方法1

  • 支持不大于0-255的范围。但可以回退到接近2(速度稍慢)
  • 每个值始终使用一个字节
  • 截断不完整的余数
    if(buffer[i]>=exclusiveLimit)
  • 调整所需的量程大小。在超出排他限值后,模数保持完全平衡
  • (使用位掩码而不是模数是一种较慢的方法)
  • 如果你想要一个0-16的范围(即17个不同的值),那么17可以放入一个字节15次。有一个值必须丢弃[255],否则模数就可以了
方法1的代码

    const string lookupCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

    static void TestRandomString()
    {
        Console.WriteLine("A random string of 100 characters:");

        int[] randomCharacterIndexes = new int[100];
        SecureRangeOriginal(randomCharacterIndexes, lookupCharacters.Length);
        var sb = new StringBuilder();
        for (int i = 0; i < randomCharacterIndexes.Length; i++)
        {
            sb.Append(lookupCharacters[randomCharacterIndexes[i]]);
        }
        Console.WriteLine(sb.ToString());

        Console.WriteLine();
    }

    static void SecureRangeOriginal(int[] result, int maxInt)
    {
        if (maxInt > 256)
        {
            //If you copy this code, you can remove this line and replace it with `throw new Exception("outside supported range");`
            SecureRandomIntegerRange(result, 0, result.Length, 0, maxInt);  //See git repo for implementation.
            return;
        }

        var maxMultiples = 256 / maxInt; //Finding the byte number boundary above the provided lookup length - the number of bytes
        var exclusiveLimit = (maxInt * maxMultiples); //Expressing that boundary (number of bytes) as an integer

        var length = result.Length;
        var resultIndex = 0;

        using (var provider = new RNGCryptoServiceProvider())
        {
            var buffer = new byte[length];

            while (true)
            {
                var remaining = length - resultIndex;
                if (remaining == 0)
                    break;

                provider.GetBytes(buffer, 0, remaining);

                for (int i = 0; i < remaining; i++)
                {
                    if (buffer[i] >= exclusiveLimit)
                        continue;

                    var index = buffer[i] % maxInt;
                    result[resultIndex++] = index;
                }
            }
        }
    }
const string lookupCharacters=“abcdefghijklmnopqrstuvxyzabcdefghijklmnopqrstuvxyz1234567890”;
静态void TestRandomString()
{
WriteLine(“100个字符的随机字符串:”);
int[]randomCharacterIndex=新int[100];
SecureRangeOriginal(RandomCharacterIndex、lookupCharacters.Length);
var sb=新的StringBuilder();
for(int i=0;i256)
{
//如果复制此代码,则可以删除此行并将其替换为“抛出新异常”(“超出支持范围”)`
SecureRandomIntegerage(result,0,result.Length,0,maxInt);//有关实现,请参阅git repo。
返回;
}
var maxMultiples=256/maxInt;//查找提供的查找长度(字节数)上方的字节数边界
var exclusiveLimit=(maxInt*maxMultiples);//将该边界(字节数)表示为整数
变量长度=结果长度;
var resultIndex=0;
使用(var provider=new RNGCryptoServiceProvider())
{
var buffer=新字节[长度];
while(true)
{
剩余变量=长度-结果索引;
如果(剩余==0)
打破
GetBytes(缓冲区,0,剩余);
对于(int i=0;i<剩余;i++)
{
if(缓冲区[i]>=exclusiveLimit)
继续;
var指数=缓冲区[i]%最大值;
结果[resultIndex++]=索引;
}
}
}
}
方法2

  • 技术范围从0到ulong。可以支持最大值
  • 将RNGCryptoServiceProvider字节视为位流
  • 计算每个数字所需的base2位长度
  • 从随机比特流中获取下一个数字
  • 如果该数字仍然大于所需范围,则放弃
结果:

  • 有关测试线束的最新结果,请参见存储库
  • 这两种方法似乎都有一个适当平衡的数字分布
  • 方法1的速度更快[859ms],但它只对单个字节有效
  • 方法2比方法1慢一点[3038ms],但它可以跨字节边界工作。它丢弃更少的位,这在随机流输入成为瓶颈(例如,不同的算法)时非常有用
  • 这两种方法的混合实现了两个方面的最佳效果:
     private string sifreuretimi(int sayı) //3
        {
            Random rastgele = new Random();  
            StringBuilder sb = new StringBuilder();
            char karakter1 = ' ', karakter2 = ' ', karakter3 = ' ';
            int ascii1, ascii2, ascii3 = 0;
    
            for (int i = 0; i < sayı/3; i++)
            {
                ascii1 = rastgele.Next(48,58);
                karakter1 = Convert.ToChar(ascii1);
    
                ascii2 = rastgele.Next(65, 91);
                karakter2 = Convert.ToChar(ascii2);
    
                ascii3 = rastgele.Next(97, 123);
                karakter3 = Convert.ToChar(ascii3);
    
                sb.Append(karakter1);
                sb.Append(karakter2);
                sb.Append(karakter3);
            }
            return sb.ToString();
        }
    
    //note: using text as HEX makes the result longer
    var crypt = new RNGCryptoServiceProvider();
    var sb = new StringBuilder();
    var buf = new byte[10]; //length: should be larger
    crypt.GetBytes(buf);
    
    //gives a "valid" range of: "0123456789ABCDEF"   
    foreach (byte b in buf)
        sb.AppendFormat("{0:x2}", b); //applies "text as hex" encoding
    
    //sb contains a RNGCryptoServiceProvider based "string"
    
    //note: added + and / chars. could be any of them
    const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+/";
    
    var crypt = new RNGCryptoServiceProvider();
    var sb = new StringBuilder();
    var buf = new byte[10]; //length: should be larger
    crypt.GetBytes(buf); //get the bytes
    
    foreach (byte b in buf)
        sb.Append(valid[b%64]);
    
    var crypt = new RNGCryptoServiceProvider();
    // = padding characters might be added to make the last encoded block 
    // contain four Base64 characters. 
    // which is actually an additional character
    var buf = new byte[10]; 
    crypt.GetBytes(buf);
    
    //contains a RNGCryptoServiceProvider random string, which is fairly readable
    //and contains max 65 different characters.
    //you can limit this to 64, by specifying a different array length.
    //because log2(64) = 6, and 24 = 4 x 6 = 3 x 8
    //all multiple of 3 bytes are a perfect fit.  (e.g.: 3, 6, 15, 30, 60)
    string result = Convert.ToBase64String(buf);
    
    private static string GenerateRandomSecret()
    {
        var validChars = Enumerable.Range('A', 26)
            .Concat(Enumerable.Range('a', 26))
            .Concat(Enumerable.Range('0', 10))
            .Select(i => (char)i)
            .ToArray();
    
        var randomByte = new byte[64 + 1]; // Max Length + Length
    
        using (var rnd = new RNGCryptoServiceProvider())
        {
            rnd.GetBytes(randomByte);
    
            var secretLength = 32 + (int)(32 * (randomByte[0] / (double)byte.MaxValue));
    
            return new string(
                randomByte
                    .Skip(1)
                    .Take(secretLength)
                    .Select(b => (int) ((validChars.Length - 1) * (b / (double) byte.MaxValue)))
                    .Select(i => validChars[i])
                    .ToArray()
            );
        }
    }
    
    public static string RandomString(int length)
    {
        const string alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        var res = new StringBuilder(length);
        using (var rng = new RNGCryptoServiceProvider())
        {
            int count = (int)Math.Ceiling(Math.Log(alphabet.Length, 2) / 8.0);
            Debug.Assert(count <= sizeof(uint));
            int offset = BitConverter.IsLittleEndian ? 0 : sizeof(uint) - count;
            int max = (int)(Math.Pow(2, count*8) / alphabet.Length) * alphabet.Length;
            byte[] uintBuffer = new byte[sizeof(uint)];
    
            while (res.Length < length)
            {
                rng.GetBytes(uintBuffer, offset, count);
                uint num = BitConverter.ToUInt32(uintBuffer, 0);
                if (num < max)
                {
                    res.Append(alphabet[(int) (num % alphabet.Length)]);
                }
            }
        }
    
        return res.ToString();
    }