如何生成一定范围内的加密安全随机整数?

问题描述 投票:0回答:5

我必须为生成密码的程序生成给定范围内的统一、安全的随机整数。现在我用这个:

RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] rand = new byte[4];
rng.GetBytes(rand);
int i = BitConverter.ToUInt16(rand, 0);
int result = i%max;   // max is the range's upper bound (the lower is 0)

此方法用于加密目的安全吗?如果没有的话我该怎么办?

c# .net .net-core random cryptography
5个回答
16
投票

您可以查看来自 niik/CryptoRandom.cs 的 CryptoRandom 类,这是 Stephen Toub 和 Shawn Farkas 的 original 版本。在本课程中,他们实现了几个似乎在加密上安全的随机生成器。

我在我的项目中使用了以下版本来生成随机整数。

public class RandomGenerator
{
    readonly RNGCryptoServiceProvider csp;

    public RandomGenerator()
    {
        csp = new RNGCryptoServiceProvider();
    }

    public int Next(int minValue, int maxExclusiveValue)
    {
        if (minValue >= maxExclusiveValue)
            throw new ArgumentOutOfRangeException("minValue must be lower than maxExclusiveValue");

        long diff = (long)maxExclusiveValue - minValue;
        long upperBound = uint.MaxValue / diff * diff;

        uint ui;
        do
        {
            ui = GetRandomUInt();
        } while (ui >= upperBound);
        return (int)(minValue + (ui % diff));
    }

    private uint GetRandomUInt()
    {
        var randomBytes = GenerateRandomBytes(sizeof(uint));
        return BitConverter.ToUInt32(randomBytes, 0);
    }

    private byte[] GenerateRandomBytes(int bytesNumber)
    {
        byte[] buffer = new byte[bytesNumber];
        csp.GetBytes(buffer);
        return buffer;
    }
}

13
投票

接受的答案中有两个问题。

  • 没有正确处理一次性csp
  • minvalue 等于 maxvalue 时,它会抛出错误(标准 random 方法不会)

修改代码

using System;
using System.Security.Cryptography;

namespace CovidMassTesting.Helpers
{
    /// <summary>
    /// Secure random generator
    ///
    /// <https://stackoverflow.com/questions/42426420/how-to-generate-a-cryptographically-secure-random-integer-within-a-range>
    ///
    /// </summary>
    public class RandomGenerator : IDisposable
    {
        private readonly RNGCryptoServiceProvider csp;

        /// <summary>
        /// Constructor
        /// </summary>
        public RandomGenerator()
        {
            csp = new RNGCryptoServiceProvider();
        }

        /// <summary>
        /// Get random value
        /// </summary>
        /// <param name="minValue"></param>
        /// <param name="maxExclusiveValue"></param>
        /// <returns></returns>
        public int Next(int minValue, int maxExclusiveValue)
        {
            if (minValue == maxExclusiveValue)
                return minValue;

            if (minValue > maxExclusiveValue)
            {
                throw new ArgumentOutOfRangeException($"{nameof(minValue)} must be lower than {nameof(maxExclusiveValue)}");
            }

            var diff = (long)maxExclusiveValue - minValue;
            var upperBound = uint.MaxValue / diff * diff;

            uint ui;
            do
            {
                ui = GetRandomUInt();
            } while (ui >= upperBound);

            return (int)(minValue + (ui % diff));
        }

        private uint GetRandomUInt()
        {
            var randomBytes = GenerateRandomBytes(sizeof(uint));
            return BitConverter.ToUInt32(randomBytes, 0);
        }

        private byte[] GenerateRandomBytes(int bytesNumber)
        {
            var buffer = new byte[bytesNumber];
            csp.GetBytes(buffer);
            return buffer;
        }

        private bool _disposed;

        /// <summary>
        /// Public implementation of Dispose pattern callable by consumers.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Protected implementation of Dispose pattern.
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                // Dispose managed state (managed objects).
                csp?.Dispose();
            }

            _disposed = true;
        }
    }
}

使用方法

/// <summary>
        /// Generates a random password,
        /// respecting the given strength requirements.
        /// </summary>
        /// <param name="opts">A valid PasswordOptions object
        /// containing the password strength requirements.</param>
        /// <returns>A random password</returns>
        public static string GenerateRandomPassword(PasswordOptions opts = null)
        {
            if (opts == null) opts = new PasswordOptions()
            {
                RequiredLength = 10,
                RequiredUniqueChars = 4,
                RequireDigit = true,
                RequireLowercase = true,
                RequireNonAlphanumeric = true,
                RequireUppercase = true
            };

            string[] randomChars = new[] {
                "ABCDEFGHJKLMNOPQRSTUVWXYZ",    // Uppercase
                "abcdefghijkmnopqrstuvwxyz",    // Lowercase
                "0123456789",                   // Digits
                "!@$?_-"                        // Non-alphanumeric
            };

            using RandomGenerator rand = new RandomGenerator();
            List<char> chars = new List<char>();

            if (opts.RequireUppercase)
                chars.Insert(rand.Next(0, chars.Count),
                    randomChars[0][rand.Next(0, randomChars[0].Length)]);

            if (opts.RequireLowercase)
                chars.Insert(rand.Next(0, chars.Count),
                    randomChars[1][rand.Next(0, randomChars[1].Length)]);

            if (opts.RequireDigit)
                chars.Insert(rand.Next(0, chars.Count),
                    randomChars[2][rand.Next(0, randomChars[2].Length)]);

            if (opts.RequireNonAlphanumeric)
                chars.Insert(rand.Next(0, chars.Count),
                    randomChars[3][rand.Next(0, randomChars[3].Length)]);

            for (int i = chars.Count; i < opts.RequiredLength
                || chars.Distinct().Count() < opts.RequiredUniqueChars; i++)
            {
                string rcs = randomChars[rand.Next(0, randomChars.Length)];
                chars.Insert(rand.Next(0, chars.Count),
                    rcs[rand.Next(0, rcs.Length)]);
            }

            return new string(chars.ToArray());
        }

12
投票

在 .NET 6 中,之前答案中使用的

RNGCryptoServiceProvider
现在已过时

对于加密随机数,只需使用

RandomNumberGenerator
静态方法,例如:

var byteArray = RandomNumberGenerator.GetBytes(24);

6
投票

从你的代码中我可以看到,你想从一个区间中获取一个随机整数。

.NET 中包含一个新的加密随机数生成器(自 Core 3.0、Core 3.1、.NET 5、.NET 6、.NET 7 RC 1 和 .NET Standard 2.1 版本起)。

正如 jws 提到的,以前使用的类 RNGCryptoServiceProvider 已被弃用。

您可以使用这个辅助方法。它还可以轻松替换不安全的 System.Random 的 .Next 方法:

/// <summary>
/// Generate a secure random number
/// </summary>
/// <param name="fromInclusive">Random number interval (min, including this number)</param>
/// <param name="toExclusive">Random number interval (max, excluding this number)</param>
/// <returns></returns>
private int RandomNumber(int fromInclusive, int toExclusive)
=> System.Security.Cryptography.RandomNumberGenerator.GetInt32(fromInclusive, toExclusive);

要模拟掷骰子,请像这样使用

var getNumber = RandomNumber(1, 7); // including 1, excluding 7 => 1 .. 6

如果您更喜欢使用 .Next() 的“旧方式”,您可以像这样创建一个类(请注意,不是种子,而是 fromInclusive 和 toExclusive 参数):

public class SecureRandom
{
    private int fromInc, toExcl;
    public SecureRandom(int toExclusive = 2) => Init(0, toExclusive);
    public SecureRandom(int fromInclusive, int toExclusive) 
           => Init(fromInclusive, toExclusive);
    private void Init(int fromInclusive, int toExclusive)
    {
        fromInc = fromInclusive; toExcl = toExclusive;
    }
    
    public int Next() => RandomNumber(fromInc, toExcl);

    public static int RandomNumber(int fromInclusive, int toExclusive)
    => System.Security.Cryptography.RandomNumberGenerator.GetInt32(fromInclusive, toExclusive);
}

示例:

// always the same interval in a loop:
var rnd = new SecureRandom(1, 7); 
for (int i = 0; i < 100; i++)
{
    Console.WriteLine(rnd.Next()); // roll the dice 100 times
}

// alternative usage (without creating an instance):
Console.WriteLine(SecureRandom.RandomNumber(1, 7));

注:

  • 此版本不再需要从加密类获取实例 - 您只需调用它即可获取下一个随机数。

  • 还有一个

    GetInt32(...)
    的重载,它采用一个参数作为最大独占值,从最小值 0 开始。如果您需要,请随时更新代码并为静态函数创建另一个重载方法
    RandomNumber 
    .


0
投票

在新项目中,它说 RNGCryptoServiceProvider 已过时,这是等效版本,但在 System.Security.Cryptography 命名空间中使用 RandomNumberGenerator ,它并未过时且加密安全:

public class SecureRandomNumberGenerator: IDisposable
{
   private RandomNumberGenerator rng = RandomNumberGenerator.Create();

   public int GenerateRandomNumberInRange(int minValue, int maxValue)
{
    if (minValue >= maxValue)
    {
        throw new ArgumentOutOfRangeException(nameof(minValue), "minValue must be less than maxValue");
    }

    int range = maxValue - minValue + 1;
    byte[] uint32Buffer = new byte[4];

    int result;
    do
    {
        rng.GetBytes(uint32Buffer);
        uint randomUint = BitConverter.ToUInt32(uint32Buffer, 0);
        result = (int)(randomUint % range);
    } while (result < 0 || result >= range);

    return minValue + result;
}

public void Dispose()
{
    rng.Dispose();
}

}

用法如下:

using (var num = new SecureRandomNumberGenerator())
{
  for (int i = 0; i < 100; i++)
  {
       var number = num.GenerateRandomNumberInRange(5, 10);
       Console.WriteLine(number);
   }
}
© www.soinside.com 2019 - 2024. All rights reserved.