我该如何在C#中生成一个包含8个随机字母和数字的字符串?
我该如何在C#中生成一个包含8个随机字母和数字的字符串?
我听说LINQ是新黑,所以这是我尝试使用LINQ:
private static Random random = new Random();
public static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
(注意:使用 Random
类并不适合用于任何安全相关的事情,例如创建密码或令牌。如果需要一个强大的随机数生成器,请使用 RNGCryptoServiceProvider
类。)
return new string(Enumerable.Range(1, length).Select(_ => chars[random.Next(chars.Length)]).ToArray());
- Tyson Williamsvar chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[8];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
var finalString = new String(stringChars);
不如 Linq 解决方案优雅。
(注意: 使用 Random
类不适合用于任何安全相关的操作,例如创建密码或令牌。如果需要强随机数生成器,请使用 RNGCryptoServiceProvider
类。)
GetRandomFileName
解决方案更快,但不允许控制使用的字符,并且最大可能长度为11个字符。Douglas的Guid
解决方案非常快,但字符受限于A-F0-9,最大可能长度为32个字符。 - LukeHGetRandomFileName
的结果,但这样做会(a)失去性能优势,(b)使你的代码变得更加复杂。 - LukeHSystem.Random
不适合用于安全。 - CodesInChaos更新至 .NET 6。RNGCryptoServiceProvider 已被标记为过时。现在应该调用 RandomNumberGenerator.Create()。答案中的代码已做相应更新。
基于评论进行更新。原始实现生成 a-h 的概率约为 1.95%,其余字符的概率约为 1.56%。更新后,所有字符的概率均约为 1.61%。
框架支持 - .NET Core 3(以及支持 .NET Standard 2.1 或以上版本的未来平台)提供了一个加密安全的方法 RandomNumberGenerator.GetInt32() 来生成所需范围内的随机整数。
与其他一些方案不同,这个方案是加密安全的。
using System;
using System.Security.Cryptography;
using System.Text;
namespace UniqueKey
{
public class KeyGenerator
{
internal static readonly char[] chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
public static string GetUniqueKey(int size)
{
byte[] data = new byte[4*size];
using (var crypto = RandomNumberGenerator.Create())
{
crypto.GetBytes(data);
}
StringBuilder result = new StringBuilder(size);
for (int i = 0; i < size; i++)
{
var rnd = BitConverter.ToUInt32(data, i * 4);
var idx = rnd % chars.Length;
result.Append(chars[idx]);
}
return result.ToString();
}
public static string GetUniqueKeyOriginal_BIASED(int size)
{
char[] chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
byte[] data = new byte[size];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
StringBuilder result = new StringBuilder(size);
foreach (byte b in data)
{
result.Append(chars[b % (chars.Length)]);
}
return result.ToString();
}
}
}
根据这里的备选方案讨论并根据下面的评论进行更新/修改。
这是一个小的测试工具,演示了旧输出和更新输出中字符的分布情况。有关随机性分析的深入讨论,请查看random.org。
using System;
using System.Collections.Generic;
using System.Linq;
using UniqueKey;
namespace CryptoRNGDemo
{
class Program
{
const int REPETITIONS = 1000000;
const int KEY_SIZE = 32;
static void Main(string[] args)
{
Console.WriteLine("Original BIASED implementation");
PerformTest(REPETITIONS, KEY_SIZE, KeyGenerator.GetUniqueKeyOriginal_BIASED);
Console.WriteLine("Updated implementation");
PerformTest(REPETITIONS, KEY_SIZE, KeyGenerator.GetUniqueKey);
Console.ReadKey();
}
static void PerformTest(int repetitions, int keySize, Func<int, string> generator)
{
Dictionary<char, int> counts = new Dictionary<char, int>();
foreach (var ch in UniqueKey.KeyGenerator.chars) counts.Add(ch, 0);
for (int i = 0; i < REPETITIONS; i++)
{
var key = generator(KEY_SIZE);
foreach (var ch in key) counts[ch]++;
}
int totalChars = counts.Values.Sum();
foreach (var ch in UniqueKey.KeyGenerator.chars)
{
Console.WriteLine($"{ch}: {(100.0 * counts[ch] / totalChars).ToString("#.000")}%");
}
}
}
}
更新于2022年7月25日
根据评论中的一个问题,我想知道这个分布是否真的是随机的。
我不是统计学家,但我可能可以在电视上扮演一个。如果有实际的统计学家想发表意见,那将是非常欢迎的。
有62个可能的输出值(A-Za-Z0-9)和int.MaxValue
个数字用于选择一个数组索引。int.MaxValue % 62
等于1,因此有一个字符会比其他字符被选中多大约四十亿倍。我们可以在索引之前通过随机旋转输出值的数组来进一步减少选择偏差。
T检验或其他统计量将是确定输出结果是否存在偏差的最佳方法,但这并不是我午休时间内能完成的事情。因此,我给你们留下了以上代码的修改版本,它可以衡量期望值的偏差。请注意,它趋向于零。
using System.Security.Cryptography;
using System.Text;
const int REPETITIONS = 1_000_000;
const int KEY_SIZE = 32;
int TASK_COUNT = Environment.ProcessorCount - 1;
var expectedPercentage = 100.0 / KeyGenerator.chars.Length;
var done = false;
var iterationNr = 1;
var totalRandomSymbols = 0L;
var grandTotalCounts = new Dictionary<char, long>();
foreach (var ch in KeyGenerator.chars) grandTotalCounts.Add(ch, 0);
while (!done)
{
var experiments = Enumerable.Range(0, TASK_COUNT).Select(i => Task.Run(Experiment)).ToArray();
Task.WaitAll(experiments);
var totalCountsThisRun = experiments.SelectMany(e => e.Result)
.GroupBy(e => e.Key)
.Select(e => new { e.Key, Count = e.Select(_ => _.Value).Sum() })
.ToDictionary(e => e.Key, e => e.Count);
foreach (var ch in KeyGenerator.chars)
grandTotalCounts[ch] += totalCountsThisRun[ch];
var totalChars = grandTotalCounts.Values.Sum();
totalRandomSymbols += totalChars;
var distributionScores = KeyGenerator.chars.Select(ch =>
new
{
Symbol = ch,
OverUnder = (100.0 * grandTotalCounts[ch] / totalChars) - expectedPercentage
});
Console.WriteLine($"Iteration {iterationNr++}. Total random symbols: {totalRandomSymbols:N0}");
foreach (var chWithValue in distributionScores.OrderByDescending(c => c.OverUnder))
{
Console.WriteLine($"{chWithValue.Symbol}: {chWithValue.OverUnder:#.00000}%");
}
done = Console.KeyAvailable;
}
Dictionary<char, long> Experiment()
{
var counts = new Dictionary<char, long>();
foreach (var ch in KeyGenerator.chars) counts.Add(ch, 0);
for (int i = 0; i < REPETITIONS; i++)
{
var key = KeyGenerator.GetUniqueKey(KEY_SIZE);
foreach (var ch in key) counts[ch]++;
}
return counts;
}
public class KeyGenerator
{
internal static readonly char[] chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
public static string GetUniqueKey(int size)
{
byte[] data = new byte[4 * size];
using (var crypto = RandomNumberGenerator.Create())
{
crypto.GetBytes(data);
}
StringBuilder result = new StringBuilder(size);
for (int i = 0; i < size; i++)
{
var rnd = BitConverter.ToUInt32(data, i * 4);
var idx = rnd % chars.Length;
result.Append(chars[idx]);
}
return result.ToString();
}
}
解决方案1 - 最大范围,长度最灵活
string get_unique_string(int string_length) {
using(var rng = new RNGCryptoServiceProvider()) {
var bit_count = (string_length * 6);
var byte_count = ((bit_count + 7) / 8); // rounded up
var bytes = new byte[byte_count];
rng.GetBytes(bytes);
return Convert.ToBase64String(bytes);
}
}
与使用GUID相比,此解决方案具有更大的范围,因为GUID有一些固定位始终相同,因此不是随机的,例如十六进制中的第13个字符始终为“4”- 至少在版本6 GUID中。
此解决方案还允许您生成任意长度的字符串。
解决方案2- 一行代码-适用于最多22个字符
Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 8);
如果使用解决方案1生成的字符串与GUID中固定位导致范围不同,那么你将无法生成同样长的字符串,但在许多情况下,这种方法将能够胜任。
解决方案3 - 代码稍微少一些
Guid.NewGuid().ToString("n").Substring(0, 8);
这里大部分是为了历史记录而保留。它使用的代码稍微少一些,但是换来的代价是范围更小 - 因为它使用十六进制而不是base64,所以表示相同范围需要更多的字符,与其他解决方案相比。
这意味着发生冲突的机会更多 - 对生成的8个字符字符串进行100,000次迭代测试,有一个重复。
这里有一个例子,我从Sam Allen在Dot Net Perls的例子中偷来的。
如果你只需要8个字符,那么可以使用System.IO命名空间中的Path.GetRandomFileName()方法。Sam说在这里使用"Path.GetRandomFileName"方法有时更好,因为它使用RNGCryptoServiceProvider获得更好的随机性。然而,它只限于11个随机字符。
GetRandomFileName总是返回一个12个字符的字符串,在第9个字符处带有一个句点。因此,你需要去掉句点(因为那不是随机的),然后从字符串中取8个字符。实际上,你只需取前8个字符而不用担心句点。
public string Get8CharacterRandomString()
{
string path = Path.GetRandomFileName();
path = path.Replace(".", ""); // Remove period.
return path.Substring(0, 8); // Return 8 character string
}
提示:感谢Sam
我代码的主要目标是:
第一个属性通过将64位值模除字母表大小来实现。对于小字母表(例如问题中的62个字符),这会导致可忽略的偏差。第二和第三个属性是通过使用RNGCryptoServiceProvider
而不是System.Random
来实现的。
using System;
using System.Security.Cryptography;
public static string GetRandomAlphanumericString(int length)
{
const string alphanumericCharacters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789";
return GetRandomString(length, alphanumericCharacters);
}
public static string GetRandomString(int length, IEnumerable<char> characterSet)
{
if (length < 0)
throw new ArgumentException("length must not be negative", "length");
if (length > int.MaxValue / 8) // 250 million chars ought to be enough for anybody
throw new ArgumentException("length is too big", "length");
if (characterSet == null)
throw new ArgumentNullException("characterSet");
var characterArray = characterSet.Distinct().ToArray();
if (characterArray.Length == 0)
throw new ArgumentException("characterSet must not be empty", "characterSet");
var bytes = new byte[length * 8];
var result = new char[length];
using (var cryptoProvider = new RNGCryptoServiceProvider())
{
cryptoProvider.GetBytes(bytes);
}
for (int i = 0; i < length; i++)
{
ulong value = BitConverter.ToUInt64(bytes, i * 8);
result[i] = characterArray[value % (uint)characterArray.Length];
}
return new string(result);
}
最简单的:
public static string GetRandomAlphaNumeric()
{
return Path.GetRandomFileName().Replace(".", "").Substring(0, 8);
}
如果您硬编码字符数组并依赖于 System.Random
, 您可以获得更好的性能:
public static string GetRandomAlphaNumeric()
{
var chars = "abcdefghijklmnopqrstuvwxyz0123456789";
return new string(chars.Select(c => chars[random.Next(chars.Length)]).Take(8).ToArray());
}
如果你担心英文字母可能会在某个时候改变,导致你失去业务,那么你可以避免使用硬编码,但是性能会稍微差一些(与Path.GetRandomFileName
方法相当)。
如果您担心英文字母可能会出现问题,导致您失去业务,那么您可以避免硬编码,但应该表现略差(与Path.GetRandomFileName
方法相当)。
public static string GetRandomAlphaNumeric()
{
var chars = 'a'.To('z').Concat('0'.To('9')).ToList();
return new string(chars.Select(c => chars[random.Next(chars.Length)]).Take(8).ToArray());
}
public static IEnumerable<char> To(this char start, char end)
{
if (end < start)
throw new ArgumentOutOfRangeException("the end char should not be less than start char", innerException: null);
return Enumerable.Range(start, end - start + 1).Select(i => (char)i);
}
如果您可以将这两种方法作为System.Random
实例的扩展方法,那么它们看起来更好。
chars.Select
很丑陋,因为它依赖于输出大小最多为字母表大小。 - CodesInChaoschars.Count >= n
时,chars.Select()
.Take(n)才能正常工作。在一个你实际上不使用的序列上进行选择有点不直观,特别是带有隐式长度约束。我更愿意使用
Enumerable.Range或
Enumerable.Repeat`。这篇帖子中的各种答案进行了一些性能比较:
// what's available
public static string possibleChars = "abcdefghijklmnopqrstuvwxyz";
// optimized (?) what's available
public static char[] possibleCharsArray = possibleChars.ToCharArray();
// optimized (precalculated) count
public static int possibleCharsAvailable = possibleChars.Length;
// shared randomization thingy
public static Random random = new Random();
// https://dev59.com/qHM_5IYBdhLWcg3whzjx#1344242
public string LinqIsTheNewBlack(int num) {
return new string(
Enumerable.Repeat(possibleCharsArray, num)
.Select(s => s[random.Next(s.Length)])
.ToArray());
}
// https://dev59.com/qHM_5IYBdhLWcg3whzjx#1344258
public string ForLoop(int num) {
var result = new char[num];
while(num-- > 0) {
result[num] = possibleCharsArray[random.Next(possibleCharsAvailable)];
}
return new string(result);
}
public string ForLoopNonOptimized(int num) {
var result = new char[num];
while(num-- > 0) {
result[num] = possibleChars[random.Next(possibleChars.Length)];
}
return new string(result);
}
public string Repeat(int num) {
return new string(new char[num].Select(o => possibleCharsArray[random.Next(possibleCharsAvailable)]).ToArray());
}
// https://dev59.com/qHM_5IYBdhLWcg3whzjx#1518495
public string GenerateRandomString(int num) {
var rBytes = new byte[num];
random.NextBytes(rBytes);
var rName = new char[num];
while(num-- > 0)
rName[num] = possibleCharsArray[rBytes[num] % possibleCharsAvailable];
return new string(rName);
}
//SecureFastRandom - or SolidSwiftRandom
static string GenerateRandomString(int Length) //Configurable output string length
{
byte[] rBytes = new byte[Length];
char[] rName = new char[Length];
SolidSwiftRandom.GetNextBytesWithMax(rBytes, biasZone);
for (var i = 0; i < Length; i++)
{
rName[i] = charSet[rBytes[i] % charSet.Length];
}
return new string(rName);
}
在 LinqPad 中进行测试,对于字符串长度为 10,生成以下结果:
- Linq 方法 = chdgmevhcy [10]
- for 循环方法 = gtnoaryhxr [10]
- Select 方法 = rsndbztyby [10]
- 随机字符串生成方法 = owyefjjakj [10]
- 安全快速随机数生成方法 = VzougLYHYP [10]
- 无缓存的安全快速随机数生成方法 = oVQXNGmO1S [10]
性能指标可能会有轻微差异,很少情况下非优化方法实际上更快,有时候 for 循环和随机字符串生成方法会互相交替领先。
- Linq 方法 (10000 次) = 96762 个节拍已过去 (9.6762 毫秒)
- for 循环方法 (10000 次) = 28970 个节拍已过去 (2.897 毫秒)
- 非优化的 for 循环方法 (10000 次) = 33336 个节拍已过去 (3.3336 毫秒)
- Repeat 方法 (10000 次) = 78547 个节拍已过去 (7.8547 毫秒)
- 随机字符串生成方法 (10000 次) = 27416 个节拍已过去 (2.7416 毫秒)
- 安全快速随机数生成方法 (10000 次) = 13176 个节拍已过去 (5 毫秒) 最低 [不同的机器]
- 无缓存的安全快速随机数生成方法 (10000 次) = 39541 个节拍已过去 (17 毫秒) 最低 [不同的机器]
var many = 10000; Assert.AreEqual(many, new bool[many].Select(o => EachRandomizingMethod(10)).Distinct().Count());
,其中你需要将 EachRandomizingMethod
替换为每个方法。 - drzaus一行代码Membership.GeneratePassword()
就可以解决问题 :)
这里有一个演示相同的功能。
我的简单一行代码对我有效 :)
string random = string.Join("", Guid.NewGuid().ToString("n").Take(8).Select(o => o));
Response.Write(random.ToUpper());
Response.Write(random.ToLower());
为了对任意长度的字符串进行拓展
public static string RandomString(int length)
{
//length = length < 0 ? length * -1 : length;
var str = "";
do
{
str += Guid.NewGuid().ToString().Replace("-", "");
}
while (length > str.Length);
return str.Substring(0, length);
}