我必须为生成密码的程序生成给定范围内的统一、安全的随机整数。现在我用这个:
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)
此方法用于加密目的安全吗?如果没有的话我该怎么办?
您可以查看来自 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;
}
}
接受的答案中有两个问题。
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());
}
在 .NET 6 中,之前答案中使用的
RNGCryptoServiceProvider
现在已过时。
对于加密随机数,只需使用
RandomNumberGenerator
静态方法,例如:
var byteArray = RandomNumberGenerator.GetBytes(24);
从你的代码中我可以看到,你想从一个区间中获取一个随机整数。
.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
.
在新项目中,它说 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);
}
}