我正在使用此代码生成给定长度的随机字符串
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();
}
但是,我读到RNGCryptoServiceProvider
比Random
类更安全。如何为此函数实现RNGCryptoServiceProvider
。它应该像这个函数一样使用valid
字符串。
答案 0 :(得分:22)
由于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个有效字符等于5,9541963103868752088061235991756位(log(62)/ log(2)),因此它不会在32位数字(uint)上均匀分配。 / p>
这有什么后果? 结果,随机输出将不均匀。有效率较低的字符更有可能发生(只是一小部分,但仍然会发生)。
更准确地说,有效数组的前4个字符更有可能发生0,00000144354999199840239435286%。
为了避免这种情况,你应该使用像64那样均匀划分的数组长度(考虑在输出上使用Convert.ToBase64String,因为你可以干净地匹配64位到6个字节。
答案 1 :(得分:5)
您需要使用byte
生成随机RNGCryptoServiceProvider
,并仅将有效的string
附加到返回的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;
}
:
byte
您也可以使用模数来跳过无效的"a" cmp $toto
值,但每个字符的可能性都不均匀。
答案 2 :(得分:2)
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();
}
(我将该方法设为静态,因为它不使用任何实例数据。)
答案 3 :(得分:2)
请参阅https://bitbucket.org/merarischroeder/number-range-with-no-bias/
我确信我之前已经通过安全实施,没有偏见和良好的性能来回答这个问题。如果是,请发表评论。
看看Tamir的答案,我认为使用模数运算会更好,但会修剪字节值的不完整余数。我现在也在写这个答案(可能再次),因为我需要将这个解决方案引用给同行。
方法1
if (buffer[i] >= exclusiveLimit)
方法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;
}
}
}
}
方法2
结果:
答案 4 :(得分:1)
注意:我知道OP的用例偏差,但我认为它可能会帮助那些用例略有不同的人
关于将加密字节数组转换为字符串的原因可以说很多,但通常是出于某种序列化目的;因此,在这种情况下:所选字符集是任意的。
所以,如果且仅当前者为真并且不需要优化字符串的长度;您可以使用字节数组的简单十六进制表示形式,如下所示:
tag_id[]
现在你要说:但是等等:这些只有16个字符,其中OP的序列有62个。你的字符串将会更长。
“是”,我会回答,“如果这是一个问题,为什么不选择256个易于阅读和可串联的字符......或者也许64“; - )
正如@Guffa所述;除非不改变分布,否则禁止使用//note: since the choice of characters [0..9a..zA...Z] is arbitrary,
//limiting to [0..9,A..F] would seem to be a really big problem if it can be compensated
//by the length.
var rnd = new RNGCryptoServiceProvider();
var sb = new StringBuilder();
var buf = new byte[10]; //length: should be larger
rnd.GetBytes(buf);
//gives a "valid" range of: "0123456789ABCDEF"
foreach (byte b in buf)
sb.AppendFormat("{0:x2}", b);
//sb contains a RNGCryptoServiceProvider based "string"
。为了实现这一点,给定均匀分布的集合,子集必须在原始集合中完全符合 x 次。
因此,将初始有效集扩展为2会得到有效结果(因为:256/64 = 4)。
代码如下:
%
请注意:在所有答案中,包括这一个,子集小于字节的256种可能性。这意味着可用的信息较少。这意味着如果您的字符串包含4个字符,则更容易破解RNGCryptoServiceProvider的原始4字节结果。
所以...现在你说:“为什么不使用64基本编码?”,好吧,如果它适合你,但要小心尾随//note: added + and / chars. could be any of them
const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+/";
var rnd = new RNGCryptoServiceProvider();
var sb = new StringBuilder();
var buf = new byte[10]; //length: should be larger
rnd.GetBytes(buf); //get the bytes
foreach (byte b in buf)
sb.Append(valid[b%64]);
,请参阅{{ 3}}:
=
请注意²:典型的用例是一些网址令牌。请注意,var rnd = new RNGCryptoServiceProvider();
var buf = new byte[60]; //length not randomly picked, see 64Base, wikipedia
rnd.GetBytes(buf);
string result = Convert.ToBase64String(buf);
符号无效为此类字符。
答案 5 :(得分:1)
我个人喜欢使用此
: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()
);
}
}
不应有任何部分需要额外的描述,但为澄清起见,此函数返回随机长度为32到64个字符的随机字符串,并且不使用%
(mod),因此应保留均匀性好一点。
我使用它在程序安装时创建一个随机盐,然后将其保存到文件中。因此,在程序运行时,生成的字符串的安全性不是特别需要考虑的问题,因为以后无论如何它都会被写入未加密的文件中。
但是,在更严重的情况下,不应将其原样使用,并且如果要将此值保留在内存中,则应转换为使用SecureString
类。在此处阅读更多信息:
但是,即使这仅适用于NetFramework,对于NetCore,您还需要找到另一种方法来保护内存中的值。在此处阅读更多信息:
https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md
答案 6 :(得分:1)
我的实现使用5,9541963103868752082081235991756位修复了该问题
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();
}
答案 7 :(得分:0)
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();
}