RNGCryptoServiceProvider GetBytes()-如何限制返回值?

时间:2018-08-24 07:56:00

标签: c# rngcryptoserviceprovider

我有此方法,该方法返回一串具有加密强度的随机字符。

首先,GetBytes()填充一个字节数组,其值从0到255。

接下来,通过从字符集中选择字符编号{byte value} % {length of character set}来构建返回字符串。

问题是我的大多数字符集的长度不能被256整除,因此结果将偏向某些字符。例如,如果字符集的长度为8、16或32个字符,则其余为0,则没有问题。

所以我在想-我可以限制GetBytes()返回的值,以使字符集的长度可以被最大值均匀除尽吗?例如,如果字符集的长度为62,则最大值应为247。

我当然可以一次只得到一个字节,如果值太高,我可以再得到一个字节。但这不是很优雅。

/// <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>
public static string Serial(string type, int length)
{
    if (length < 1) return "";
    string chars;
    switch (type)
    {
        case "HEX":
            chars = "0123456789ABCDEF"; // 16
            break;
        case "hex":
            chars = "0123456789abcdef"; // 16
            break;
        case "DEC":
        case "dec":
        case "NUM":
        case "num":
            chars = "0123456789"; // 10
            break;
        case "ALPHA":
            chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 26
            break;
        case "alpha":
            chars = "abcdefghijklmnopqrstuvwxyz"; // 26
            break;
        case "ALPHANUM":
            chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // 36
            break;
        case "alphanum":
            chars = "abcdefghijklmnopqrstuvwxyz0123456789"; // 36
            break;
        case "FULL":
        case "full":
        default:
            chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // 62
            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();
}

1 个答案:

答案 0 :(得分:1)

如评论中所述,拒绝采样是执行此操作的标准方法。我们可以通过将RNG加密提供程序的使用转移到辅助方法中来摊销一些成本,这样我们就不必逐字节处理字节了:

    public static string Serial(string type, int length)
    {
        if (length < 1) return "";
        string chars;
        switch (type)
        {
            case "HEX":
                chars = "0123456789ABCDEF"; // 16
                break;
            case "hex":
                chars = "0123456789abcdef"; // 16
                break;
            case "DEC":
            case "dec":
            case "NUM":
            case "num":
                chars = "0123456789"; // 10
                break;
            case "ALPHA":
                chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 26
                break;
            case "alpha":
                chars = "abcdefghijklmnopqrstuvwxyz"; // 26
                break;
            case "ALPHANUM":
                chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // 36
                break;
            case "alphanum":
                chars = "abcdefghijklmnopqrstuvwxyz0123456789"; // 36
                break;
            case "FULL":
            case "full":
            default:
                chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // 62
                break;
        }
        int limit = (256 / chars.Length) * chars.Length;
        StringBuilder result = new StringBuilder(length);
        foreach (byte b in SecureBytesInRange(limit,length))
        {
            result.Append(chars[b % chars.Length]);
        }
        return result.ToString();
    }
    private const int SECURE_BYTE_BUFFER_SIZE = 32;
    static IEnumerable<byte> SecureBytesInRange(int exclusiveUpperBound, int countRequired)
    {
        using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
        {
            byte[] buffer = new byte[SECURE_BYTE_BUFFER_SIZE];
            int ix = SECURE_BYTE_BUFFER_SIZE;
            int countProduced = 0;
            while (countProduced < countRequired)
            {
                if (ix == SECURE_BYTE_BUFFER_SIZE)
                {
                    crypto.GetBytes(buffer);
                    ix = 0;
                }
                while (ix < SECURE_BYTE_BUFFER_SIZE)
                {
                    if (buffer[ix] < exclusiveUpperBound)
                    {
                        yield return buffer[ix];
                        countProduced++;
                        if (countProduced == countRequired) break;
                    }
                    ix++;
                }
            }
        }
    }

正如我在注释中也指出的那样,我将为受支持的编码类型创建一个枚举,而不是使用字符串,或者,使用命名常量/属性来返回要使用的实际字符范围,因此相反,您直接传递的是chars而不是type(这还使您的函数可以与其他范围的字符一起使用,从而增加了灵活性,而不仅仅是您现在能想到的那些字符。