.net 如何在一个范围内生成一个加密安全的随机整数?

yfjy0ee7  于 2023-01-06  发布在  .NET
关注(0)|答案(4)|浏览(150)

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

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)

此方法用于加密目的安全吗?如果不安全,我应该如何做?

tcomlyy6

tcomlyy61#

你可以看看 niik/CryptoRandom.cs 中的CryptoRandom类,这是Stephen Toub和Shawn Farkas的 * 原始 * 版本,在这个类中,他们实现了几个看起来加密安全的随机数生成器。
我在我的项目中使用了下面的版本来生成随机整数。

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;
    }
}
s2j5cfk0

s2j5cfk02#

公认的答案有两个问题。

  • 未正确处置一次性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());
        }
bbuxkriu

bbuxkriu3#

在.NET 6中,前面答案中使用的RNGCryptoServiceProvider现在是obsolete
对于加密随机数,只需使用RandomNumberGenerator静态方法,例如:

var byteArray = RandomNumberGenerator.GetBytes(24);
ubbxdtey

ubbxdtey4#

从你的代码中我可以看出,你想从一个区间中得到一个随机整数。
.NET中包含了一个新的加密random number generator(从版本Core 3.0、Core 3.1、.NET 5、.NET 6、.NET 7 RC 1和.NET标准2.1开始)。
正如jws所提到的,以前使用的类RNGCryptoServiceProvider已被弃用。
你可以使用这个帮助器方法。它也可以很容易地替换不安全的System.Random的。

/// <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参数来代替seed):

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
}

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

注:

  • 这个版本不再需要从加密类中获取示例--只需调用它来获取下一个随机数。
  • 还有一个GetInt32(...)的重载,它接受一个参数作为最大互斥值,该值从最小值0开始。如果需要,请随时更新代码并创建一个重载方法。

相关问题