如何生成随机的字母数字字符串?


965

如何在C#中生成随机的8个字符的字母数字字符串?


2
您对字符集有什么限制?只是英文字符和0-9?混合大小写?
Eric J.


9
请注意,您不应使用基于Random该类的任何方法来生成密码。的播种的Random熵极低,因此并不十分安全。对密码使用加密的PRNG。
CodesInChaos 2011年

2
在这个问题中包括语言本地化会很好。特别是如果您的gui需要迎合中文或保加利亚语!
Peter Jamsmenson

15
具有如此多的赞誉和如此众多的质量答案的事情不应该被标记为已关闭。我投票要求重新开放。
约翰·科尔曼

Answers:


1683

我听说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如果需要强大的随机数生成器,请使用该类。)


23
@Alex:我已经进行了一些快速测试,并且在生成更长的字符串时(只要实际上有足够的可用内存)似乎可以线性扩展。话虽如此,但丹·里格比的答案几乎是每个测试中答案的两倍。
LukeH

6
好。如果您的标准是它使用linq并且代码描述很糟糕,那么那绝对是蜜蜂的膝盖。代码叙述和实际执行路径都相当低效和间接。不要误会我的意思,我是一个代码编写狂(我喜欢python),但这几乎是一台rube goldberg机器。
eremzeit 2011年

5
虽然这从技术上回答了这个问题,但它的输出却极具误导性。8生成随机字符听起来就像是可以有非常多的结果,而这充其量会产生2个十亿不同的结果。在实践中甚至更少。您还应该添加一个BIG FAT警告,不要将其用于任何与安全相关的内容。
CodesInChaos 2012年

41
@ xaisoft:小写字母留给读者练习。
dtb

15
下一行比给定的内存更有效的存储(因而节省了时间)return new string(Enumerable.Range(1, length).Select(_ => chars[random.Next(chars.Length)]).ToArray());
泰森·威廉姆斯

374
var 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如果需要强大的随机数生成器,请使用该类。)


4
@Alex:这不是绝对最快的答案,但它是最快的“真实”答案(即,那些可以控制使用的字符和字符串长度的答案)。
LukeH

2
@Alex:Adam Porad的GetRandomFileName解决方案速度更快,但不允许对所使用的字符进行任何控制,最大长度为11个字符。道格拉斯(Douglas)的Guid解决方案是闪电般的,但字符限制为A-F0-9,最大可能长度为32个字符。
路加福音

1
@Adam:是的,您可以合并多次调用的结果,GetRandomFileName但是(a)您将失去性能优势,并且(b)您的代码将变得更加复杂。
路加福音

1
@xaisoft在循环之外创建Random()对象的实例。如果您在很短的间隔内创建了大量Random()实例,则对.Next()的调用将返回与Random()使用基于时间的种子相同的值。
Dan Rigby 2013年

2
@xaisoft请勿将此答案用于对安全性至关重要的任何事情,例如密码。System.Random不适合安全性。
CodesInChaos

333

根据评论已更新。原始实现产生了大约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 (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
            {
                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")}%");
            }
        }
    }
}

11
对于我来说,这似乎是正确的方法-不应使用针对速度进行了优化并生成可重现的数字序列的Random()来生成随机密码,盐,熵等。另一方面,RNGCryptoServiceProvider.GetNonZeroBytes()产生不可重现的数字野生序列。
mindplay.dk 2011年

25
字母略有偏差(255%62!= 0)。尽管存在这个小缺陷,但到目前为止,它是最好的解决方案。
CodesInChaos 2012年

13
请注意,如果您想要加密强度,无偏的随机性,那么这不是好声音。(如果你不希望这样,那么为什么使用RNGCSP摆在首位?)使用MOD索引到chars数组意味着,除非你会得到偏置输出chars.Length恰好是256的一个除数
LukeH

15
减少偏差的一种可能是,请求4*maxSize随机字节,然后使用(UInt32)(BitConverter.ToInt32(data,4*i)% chars.Length。我也会用GetBytes代替GetNonZeroBytes。最后,您可以删除对的第一个呼叫GetNonZeroBytes。您没有使用它的结果。
CodesInChaos

14
有趣的事实:AZ az 0-9是62个字符。人们指出字母偏斜是因为256%62!=0。YouTube的视频ID为AZ az 0-9,“-”和“ _”会产生64个可能的字符,平均分成256个字符。巧合?我想不是!:)
qJake 2016年

200

解决方案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,与其他解决方案相比,它需要更多的字符来表示相同的范围。

这意味着更多的碰撞机会-用100,000个8个字符串的迭代进行测试以生成一个重复项。


22
您实际上生成了副本吗?令人惊讶的是5,316,911,983,139,663,491,615,228,241,121,400,000的GUID可能组合。
Alex

73
@Alex:他将GUID缩短为8个字符,因此发生碰撞的可能性比GUID高得多。
dtb

23
除了书呆子,没有人会喜欢这个。:)是的,您是完全正确的,8个字符的限制会有所不同。
Alex

31
Guid.NewGuid()。ToString(“ n”)会将破折号排除在外,无需调用Replace()。但应该指出的是,GUID只有0-9和AF。组合的数量“足够好”,但与真正的字母数字随机字符串所允许的数量相去甚远。发生碰撞的可能性为1:4,294,967,296,与32位随机整数相同。
richardtallent

32
1)GUID被设计为唯一,而不是随机的。虽然当前版本的Windows生成确实是随机的V4 GUID,但不能保证。例如,较旧版本的Windows使用V1 GUID,否则您可能会失败。2)仅使用十六进制字符会大大降低随机字符串的质量。从47到32位。3)人们低估了碰撞概率,因为他们是针对个别对给出的。如果生成100k 32位值,则它们之间可能会发生一次冲突。请参阅生日问题。
CodesInChaos

72

这是我从Dot Net Perls的 Sam Allen例子中窃取的一个例子

如果只需要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
}

PS:谢谢山姆


27
这很好。我进行了100,000次迭代,但从未使用过重复的名称。但是,我确实找到了几个粗俗的单词(英语)。除了列表中的第一个包含F ***之外,甚至都不会想到这一点。如果您将其用于用户会看到的东西,则请注意。
techturtle 2012年

3
@techturtle感谢您的警告。我认为存在任何使用字母中所有字母的随机字符串生成的粗俗单词的风险。
亚当·波拉德

漂亮简单,但不适用于长弦乐...为这个好把戏投票
Maher Abuthraa 2014年

该方法似乎只返回小写字母数字字符串。
jaybro 2014年

2
有时会有一些低俗的词汇,但是如果您将其运行足够长时间,最终它会写成莎士比亚。(只是宇宙的一生。:)
Slothario '18

38

我的代码的主要目标是:

  1. 琴弦的分布几乎是均匀的(只要它们很小,就不必担心微小的偏差)
  2. 每个参数集输出超过数十亿个字符串。如果PRNG仅产生20亿个(熵的31位)不同的值,则生成8个字符串(约47位的熵)是没有意义的。
  3. 这是安全的,因为我希望人们将其用于密码或其他安全令牌。

第一个属性是通过将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);
}

1
64 x Z和Math.Pow(2,Y)没有交集。因此,尽管增加数字可以减少偏差,但并不能消除偏差。我在下面更新了我的答案,我的方法是丢弃随机输入并替换为另一个值。
Todd

@Todd我知道它并不能消除偏差,但是我选择了此解决方案的简单性,而不是消除了实际上不相关的偏差。
CodesInChaos

我同意在大多数情况下,这实际上是无关紧要的。但是现在,我更新了我的软件,使其速度与Random一样快,并且比您的安全性更高。所有开源供所有人共享。是的,我在此上浪费了太多时间...
Todd

如果我们使用的是RNG提供程序,是否有任何方法可以避免理论上的偏见?我不确定...如果Todd是指他生成其他随机数的方式(当我们处于偏差区域时),那么这可能是错误的假设。RNG平均具有所有生成值的几乎线性分布。但这并不意味着我们不会在生成的字节之间具有局部相关性。因此,仅由于偏差带而增加的字节仍然可以给我们一些偏差,但是由于不同的原因。这种偏见很可能很小。但是在这种情况下,增加总生成字节数是更直接的方法。
Maxim

1
@Maxim您可以使用拒绝来完全消除偏差(假设基础生成器是完全随机的)。作为交换,代码可能会任意长时间运行(概率很小)。
CodesInChaos

32

最简单的:

public static string GetRandomAlphaNumeric()
{
    return Path.GetRandomFileName().Replace(".", "").Substring(0, 8);
}

如果对char数组进行硬编码并依赖,则可以获得更好的性能。 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方法)

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实例。


1
使用起来chars.Select很麻烦,因为它依赖于输出大小最多为字母大小。
CodesInChaos 2014年

@CodesInChaos我不确定我是否理解你。您的意思是说'a'.To('z')
nawfal 2014年

1
1)chars.Select().Take(n)`仅在chars.Count >= n。在您实际上不使用的序列上进行选择有点不直观,尤其是在具有隐式长度约束的情况下。我宁愿使用Enumerable.RangeEnumerable.Repeat。2)错误消息“结束字符应小于开始字符”是错误的舍入方式/缺少a not
CodesInChaos 2014年

@CodesInChaos但对我而言chars.Count是way > n。我也没有得到直觉的部分。那确实使所有用法都不合Take常理吗?我不相信。感谢您指出错字。
nawfal 2014年

4
这在theDailyWTF.com上作为CodeSOD文章进行了介绍。

22

只是此线程中各种答案的一些性能比较:

方法与设置

// 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();


// http://stackoverflow.com/a/1344242/1037948
public string LinqIsTheNewBlack(int num) {
    return new string(
    Enumerable.Repeat(possibleCharsArray, num)
              .Select(s => s[random.Next(s.Length)])
              .ToArray());
}

// http://stackoverflow.com/a/1344258/1037948
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());
}

// http://stackoverflow.com/a/1518495/1037948
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]
  • 来自循环= gtnoaryhxr [10]
  • 从选择= rsndbztyby [10]
  • 来自GenerateRandomString = owyefjjakj [10]
  • 来自SecureFastRandom = VzougLYHYP [10]
  • 来自SecureFastRandom-NoCache = oVQXNGmO1S [10]

而业绩数字往往略有不同,非常偶然NonOptimized其实是快,有时ForLoopGenerateRandomString切换谁是处于领先地位。

  • LinqIsTheNewBlack(10000x)= 96762滴答已过去(9.6762 ms)
  • ForLoop(10000x)=经过28970个滴答声(2.897 ms)
  • ForLoopNonOptimized(10000x)=经过33336个滴答声(3.3336 ms)
  • 重复(10000x)=经过78547个滴答声(7.8547 ms)
  • GenerateRandomString(10000x)=经过27416个滴答声(2.7416 ms)
  • SecureFastRandom(10000x)=最低经过13176个滴答声(5毫秒)[不同的机器]
  • SecureFastRandom-NoCache(10000x)=最少经过39541滴答(17毫秒)[不同的计算机]

3
知道哪些人制造了骗子会很有趣。
丽贝卡

@Junto-找出导致重复的结果,例如var many = 10000; Assert.AreEqual(many, new bool[many].Select(o => EachRandomizingMethod(10)).Distinct().Count());,在其中替换EachRandomizingMethod为...每种方法
drzaus 2013年


13

Eric J.编写的代码很草率(很明显这是6年前的东西了……他可能今天不会写该代码),甚至还有一些问题。

与提出的某些替代方案不同,此替代方案在密码上是合理的。

Untrue ...密码中存在偏见(如评论中所述),bcdefgh比其他密码更有可能(a不是因为GetNonZeroBytes它没有生成值为零的字节,所以偏见因为它a是平衡的),所以从密码学上来说这并不是真正的合理。

这应该纠正所有问题。

public static string GetUniqueKey(int size = 6, string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
{
    using (var crypto = new RNGCryptoServiceProvider())
    {
        var data = new byte[size];

        // If chars.Length isn't a power of 2 then there is a bias if
        // we simply use the modulus operator. The first characters of
        // chars will be more probable than the last ones.

        // buffer used if we encounter an unusable random byte. We will
        // regenerate it in this buffer
        byte[] smallBuffer = null;

        // Maximum random number that can be used without introducing a
        // bias
        int maxRandom = byte.MaxValue - ((byte.MaxValue + 1) % chars.Length);

        crypto.GetBytes(data);

        var result = new char[size];

        for (int i = 0; i < size; i++)
        {
            byte v = data[i];

            while (v > maxRandom)
            {
                if (smallBuffer == null)
                {
                    smallBuffer = new byte[1];
                }

                crypto.GetBytes(smallBuffer);
                v = smallBuffer[0];
            }

            result[i] = chars[v % chars.Length];
        }

        return new string(result);
    }
}

7

我们还使用自定义字符串random,但是我们将其实现为字符串的助手,因此它提供了一些灵活性...

public static string Random(this string chars, int length = 8)
{
    var randomString = new StringBuilder();
    var random = new Random();

    for (int i = 0; i < length; i++)
        randomString.Append(chars[random.Next(chars.Length)]);

    return randomString.ToString();
}

用法

var random = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Random();

要么

var random = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".Random(16);

7

我简单的一行代码对我有用:)

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

我喜欢的GUID方法太-感觉很轻
ozzy432836

6

另一种选择是使用Linq并将随机字符聚集到stringbuilder中。

var chars = "abcdefghijklmnopqrstuvwxyz123456789".ToArray();
string pw = Enumerable.Range(0, passwordLength)
                      .Aggregate(
                          new StringBuilder(),
                          (sb, n) => sb.Append((chars[random.Next(chars.Length)])),
                          sb => sb.ToString());

6

问题:为什么我要浪费时间Enumerable.Range而不是输入"ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"

using System;
using System.Collections.Generic;
using System.Linq;

public class Test
{
    public static void Main()
    {
        var randomCharacters = GetRandomCharacters(8, true);
        Console.WriteLine(new string(randomCharacters.ToArray()));
    }

    private static List<char> getAvailableRandomCharacters(bool includeLowerCase)
    {
        var integers = Enumerable.Empty<int>();
        integers = integers.Concat(Enumerable.Range('A', 26));
        integers = integers.Concat(Enumerable.Range('0', 10));

        if ( includeLowerCase )
            integers = integers.Concat(Enumerable.Range('a', 26));

        return integers.Select(i => (char)i).ToList();
    }

    public static IEnumerable<char> GetRandomCharacters(int count, bool includeLowerCase)
    {
        var characters = getAvailableRandomCharacters(includeLowerCase);
        var random = new Random();
        var result = Enumerable.Range(0, count)
            .Select(_ => characters[random.Next(characters.Count)]);

        return result;
    }
}

答:魔术弦很糟糕。有人注意到没有“I顶部的字符串中 ”吗?出于这个原因,我妈妈教我不要使用魔术弦。

nb 1:@dtb之类的其他人说,System.Random如果您需要加密安全性,请不要使用...

nb 2:这个答案不是最有效或最短的,但我希望有空间将答案与问题分开。我的答案的目的更多是警告魔术字符串,而不是提供新颖的创新答案。


我为什么要关心没有“ I?”
克里斯汀

1
字母数字(忽略大小写)为[A-Z0-9]。如果偶然地,您的随机字符串仅覆盖[A-HJ-Z0-9]了结果而不能覆盖整个允许范围,则可能会出现问题。
李慧夏

那怎么会有问题呢?因此它不包含I。是否因为少了一个字符而使破解更容易?包含35个字符而不是36个字符的可破解密码的统计数据是什么。我想我宁愿冒险……还是只是证明字符范围……而不要在我的代码中包括所有这些额外的垃圾。但是,就是我。我的意思是,不是说一个屁股洞。有时,我认为程序员出于复杂性的考虑而倾向于走极端复杂的路线。
克里斯汀

1
它扩展了用例。从此类随机字符串中排除诸如I和之O类的字符是很常见的,以避免人类将它们与1和混淆0。如果您不希望拥有人类可读的字符串,那么很好,但是如果有人可能需要键入它,那么删除那些字符实际上很聪明。
克里斯·普拉特

5

DTB解决方案的清洁版本。

    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    var random = new Random();
    var list = Enumerable.Repeat(0, 8).Select(x=>chars[random.Next(chars.Length)]);
    return string.Join("", list);

您的风格偏好可能会有所不同。


这比公认的答案更好,更有效。

5
 public static string RandomString(int length)
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var random = new Random();
        return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
    }

5

在查看了其他答案并考虑了CodeInChaos的评论之后,再加上CodeInChaos仍然有偏见(尽管更少),我认为需要最终的最终剪切和粘贴解决方案。因此,在更新答案时,我决定全力以赴。

有关此代码的最新版本,请访问Bitbucket上的新Hg存储库:https : //bitbucket.org/merarischroeder/secureswiftrandom。我建议您从以下位置复制并粘贴代码:https : //bitbucket.org/merarischroeder/secureswiftrandom/src/6c14b874f34a3f6576b0213379ecdf0ffc7496ea/Code/Alivate.SolidSwiftRandom/SolidSwiftRandom.cs?at=default&default-fileviewer= make-file-view-原始按钮可简化复制过程,并确保您具有最新版本,我认为此链接指向特定版本的代码,而不是最新版本)。

更新说明:

  1. 关于其他答案-如果您知道输出的长度,则不需要StringBuilder,并且在使用ToCharArray时,这将创建并填充数组(不需要先创建空数组)
  2. 关于其他答案-您应该使用NextBytes,而不是一次获取一个以提高性能
  3. 从技术上讲,您可以固定字节数组以加快访问速度。.在字节数组上进行6-8次以上迭代时,通常值得这样做。(此处未完成)
  4. 使用RNGCryptoServiceProvider获得最佳随机性
  5. 使用1MB随机数据缓冲区的缓存-基准测试表明,缓存的单字节访问速度快约1000倍-1MB占用9ms,而未缓存则为989ms。
  6. 在我的新课程中优化了对偏区的抑制

结束问题的解答:

static char[] charSet =  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();
static int byteSize = 256; //Labelling convenience
static int biasZone = byteSize - (byteSize % charSet.Length);
public string GenerateRandomString(int Length) //Configurable output string length
{
    byte[] rBytes = new byte[Length]; //Do as much before and after lock as possible
    char[] rName = new char[Length];
    SecureFastRandom.GetNextBytesMax(rBytes, biasZone);
    for (var i = 0; i < Length; i++)
    {
        rName[i] = charSet[rBytes[i] % charSet.Length];
    }
    return new string(rName);
}

但是您需要我的新(未经测试)课程:

/// <summary>
/// My benchmarking showed that for RNGCryptoServiceProvider:
/// 1. There is negligable benefit of sharing RNGCryptoServiceProvider object reference 
/// 2. Initial GetBytes takes 2ms, and an initial read of 1MB takes 3ms (starting to rise, but still negligable)
/// 2. Cached is ~1000x faster for single byte at a time - taking 9ms over 1MB vs 989ms for uncached
/// </summary>
class SecureFastRandom
{
    static byte[] byteCache = new byte[1000000]; //My benchmark showed that an initial read takes 2ms, and an initial read of this size takes 3ms (starting to raise)
    static int lastPosition = 0;
    static int remaining = 0;

    /// <summary>
    /// Static direct uncached access to the RNGCryptoServiceProvider GetBytes function
    /// </summary>
    /// <param name="buffer"></param>
    public static void DirectGetBytes(byte[] buffer)
    {
        using (var r = new RNGCryptoServiceProvider())
        {
            r.GetBytes(buffer);
        }
    }

    /// <summary>
    /// Main expected method to be called by user. Underlying random data is cached from RNGCryptoServiceProvider for best performance
    /// </summary>
    /// <param name="buffer"></param>
    public static void GetBytes(byte[] buffer)
    {
        if (buffer.Length > byteCache.Length)
        {
            DirectGetBytes(buffer);
            return;
        }

        lock (byteCache)
        {
            if (buffer.Length > remaining)
            {
                DirectGetBytes(byteCache);
                lastPosition = 0;
                remaining = byteCache.Length;
            }

            Buffer.BlockCopy(byteCache, lastPosition, buffer, 0, buffer.Length);
            lastPosition += buffer.Length;
            remaining -= buffer.Length;
        }
    }

    /// <summary>
    /// Return a single byte from the cache of random data.
    /// </summary>
    /// <returns></returns>
    public static byte GetByte()
    {
        lock (byteCache)
        {
            return UnsafeGetByte();
        }
    }

    /// <summary>
    /// Shared with public GetByte and GetBytesWithMax, and not locked to reduce lock/unlocking in loops. Must be called within lock of byteCache.
    /// </summary>
    /// <returns></returns>
    static byte UnsafeGetByte()
    {
        if (1 > remaining)
        {
            DirectGetBytes(byteCache);
            lastPosition = 0;
            remaining = byteCache.Length;
        }

        lastPosition++;
        remaining--;
        return byteCache[lastPosition - 1];
    }

    /// <summary>
    /// Rejects bytes which are equal to or greater than max. This is useful for ensuring there is no bias when you are modulating with a non power of 2 number.
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="max"></param>
    public static void GetBytesWithMax(byte[] buffer, byte max)
    {
        if (buffer.Length > byteCache.Length / 2) //No point caching for larger sizes
        {
            DirectGetBytes(buffer);

            lock (byteCache)
            {
                UnsafeCheckBytesMax(buffer, max);
            }
        }
        else
        {
            lock (byteCache)
            {
                if (buffer.Length > remaining) //Recache if not enough remaining, discarding remaining - too much work to join two blocks
                    DirectGetBytes(byteCache);

                Buffer.BlockCopy(byteCache, lastPosition, buffer, 0, buffer.Length);
                lastPosition += buffer.Length;
                remaining -= buffer.Length;

                UnsafeCheckBytesMax(buffer, max);
            }
        }
    }

    /// <summary>
    /// Checks buffer for bytes equal and above max. Must be called within lock of byteCache.
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="max"></param>
    static void UnsafeCheckBytesMax(byte[] buffer, byte max)
    {
        for (int i = 0; i < buffer.Length; i++)
        {
            while (buffer[i] >= max)
                buffer[i] = UnsafeGetByte(); //Replace all bytes which are equal or above max
        }
    }
}

对于历史记录-我对此答案的较旧解决方案使用了Random对象:

    private static char[] charSet =
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();

    static rGen = new Random(); //Must share, because the clock seed only has Ticks (~10ms) resolution, yet lock has only 20-50ns delay.
    static int byteSize = 256; //Labelling convenience
    static int biasZone = byteSize - (byteSize % charSet.Length);
    static bool SlightlyMoreSecurityNeeded = true; //Configuration - needs to be true, if more security is desired and if charSet.Length is not divisible by 2^X.
    public string GenerateRandomString(int Length) //Configurable output string length
    {
      byte[] rBytes = new byte[Length]; //Do as much before and after lock as possible
      char[] rName = new char[Length];
      lock (rGen) //~20-50ns
      {
          rGen.NextBytes(rBytes);

          for (int i = 0; i < Length; i++)
          {
              while (SlightlyMoreSecurityNeeded && rBytes[i] >= biasZone) //Secure against 1/5 increased bias of index[0-7] values against others. Note: Must exclude where it == biasZone (that is >=), otherwise there's still a bias on index 0.
                  rBytes[i] = rGen.NextByte();
              rName[i] = charSet[rBytes[i] % charSet.Length];
          }
      }
      return new string(rName);
    }

性能:

  1. SecureFastRandom - 第一次运行 = 〜9-33ms。难以察觉。持续进行:10,000次迭代中5 毫秒(有时上升到13 毫秒),平均一次迭代= 1.5微秒。。注意:通常需要2次,但有时最多需要刷新8次-取决于超出偏置区域的单个字节数
  2. 随机 - 第一次单次运行 = 〜0-1ms。难以察觉。持续进行:10,000次迭代中5ms。单个平均迭代= 0.5微秒。。大约相同的速度。

还要签出:

这些链接是另一种方法。可以将缓冲添加到此新代码库中,但是最重要的是探索各种方法来消除偏差,并确定速度和优点/缺点。


我发现了一些轻微的性能增强你的方法,这似乎是禁食的一群- stackoverflow.com/a/17092645/1037948
drzaus

5
1)为什么所有这些魔术常数?您指定的输出长度的3倍。只需将其定义为常量或参数即可。您可以使用charSet.Length代替62。2)Random没有锁定的静态意味着此代码不是线程安全的。3)降低0-255 mod 62会引入可检测的偏差。4)您不能ToString在始终返回的char数组上使用"System.Char[]"。您需要new String(rName)改用。
CodesInChaos

感谢@CodesInChaos,我那时从没想过那些事情。仍然仅使用Random类,但这应该更好。我想不出任何更好的方法来检测和纠正偏置输入。
Todd

从弱RNG(System.Random)开始,然后小心地避免自己的代码出现任何偏差是有点愚蠢的。想到“抛光粪便”一词。
CodesInChaos

@CodesInChaos现在学徒已超过他的主人
Todd

4

我知道这很可怕,但我无能为力:


namespace ConsoleApplication2
{
    using System;
    using System.Text.RegularExpressions;

    class Program
    {
        static void Main(string[] args)
        {
            Random adomRng = new Random();
            string rndString = string.Empty;
            char c;

            for (int i = 0; i < 8; i++)
            {
                while (!Regex.IsMatch((c=Convert.ToChar(adomRng.Next(48,128))).ToString(), "[A-Za-z0-9]"));
                rndString += c;
            }

            Console.WriteLine(rndString + Environment.NewLine);
        }
    }
}


4

我一直在寻找一个更具体的答案,在这里我想控制随机字符串的格式,并发现这篇文章。例如:(汽车的)车牌有特定的格式(每个国家/地区),我想创建随机的车牌。
我决定为此编写自己的Random扩展方法。(这是为了重用相同的Random对象,因为在多线程方案中可能会加倍)。我创建了一个要点(https://gist.github.com/SamVanhoutte/808845ca78b9c041e928),但还将在此处复制扩展类:

void Main()
{
    Random rnd = new Random();
    rnd.GetString("1-###-000").Dump();
}

public static class RandomExtensions
{
    public static string GetString(this Random random, string format)
    {
        // Based on http://stackoverflow.com/questions/1344221/how-can-i-generate-random-alphanumeric-strings-in-c
        // Added logic to specify the format of the random string (# will be random string, 0 will be random numeric, other characters remain)
        StringBuilder result = new StringBuilder();
        for(int formatIndex = 0; formatIndex < format.Length ; formatIndex++)
        {
            switch(format.ToUpper()[formatIndex])
            {
                case '0': result.Append(getRandomNumeric(random)); break;
                case '#': result.Append(getRandomCharacter(random)); break;
                default : result.Append(format[formatIndex]); break;
            }
        }
        return result.ToString();
    }

    private static char getRandomCharacter(Random random)
    {
        string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        return chars[random.Next(chars.Length)];
    }

    private static char getRandomNumeric(Random random)
    {
        string nums = "0123456789";
        return nums[random.Next(nums.Length)];
    }
}

4

现在具有一线风味。

private string RandomName()
{
        return new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    using (var cryptoProvider = new RNGCryptoServiceProvider())
                        cryptoProvider.GetBytes(cryptoResult);

                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());
}

2
对于每次访问都会更改的属性使用属性是相当可疑的。我建议改用一种方法。
CodesInChaos

2
RNGCryptoServiceProvider使用后应丢弃。
Tsahi Asher

我已经解决了IDisposable问题,但是仍然很可疑,为每个字母创建一个新的RNGCryptoServiceProvider。
piojo,

@CodesInChaos完成,现在是方法。
Matas Vaitkevicius

3

尝试结合两个部分:唯一性(序列,计数器或日期)和随机性

public class RandomStringGenerator
{
    public static string Gen()
    {
        return ConvertToBase(DateTime.UtcNow.ToFileTimeUtc()) + GenRandomStrings(5); //keep length fixed at least of one part
    }

    private static string GenRandomStrings(int strLen)
    {
        var result = string.Empty;

        var Gen = new RNGCryptoServiceProvider();
        var data = new byte[1];

        while (result.Length < strLen)
        {
            Gen.GetNonZeroBytes(data);
            int code = data[0];
            if (code > 48 && code < 57 || // 0-9
                code > 65 && code < 90 || // A-Z
                code > 97 && code < 122   // a-z
                )
            {
                result += Convert.ToChar(code);
            }
        }

        return result;
    }

    private static string ConvertToBase(long num, int nbase = 36)
    {
        var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //if you wish make algorithm more secure - change order of letter here

        // check if we can convert to another base
        if (nbase < 2 || nbase > chars.Length)
            return null;

        int r;
        var newNumber = string.Empty;

        // in r we have the offset of the char that was converted to the new base
        while (num >= nbase)
        {
            r = (int) (num % nbase);
            newNumber = chars[r] + newNumber;
            num = num / nbase;
        }
        // the last number to convert
        newNumber = chars[(int)num] + newNumber;

        return newNumber;
    }
}

测试:

[Test]
    public void Generator_Should_BeUnigue1()
    {
        //Given
        var loop = Enumerable.Range(0, 1000);
        //When
        var str = loop.Select(x=> RandomStringGenerator.Gen());
        //Then
        var distinct = str.Distinct();
        Assert.AreEqual(loop.Count(),distinct.Count()); // Or Assert.IsTrue(distinct.Count() < 0.95 * loop.Count())
    }

1)您可以使用字符文字而不是与这些字符关联的ASCII值。2)您的间隔匹配代码中有一个错误的错误。您需要使用<=>=而不是<>。3)我会在表达式周围添加不​​必要的括号,&&以明确它们的优先级,但这当然只是一种样式选择。
CodesInChaos

+ 1适用于消除偏差和添加测试。我不确定为什么要在随机字符串前面加上时间戳派生的字符串?另外,您仍然需要处置RNGCryptoServiceProvider
monty,

2

不使用的解决方案Random

var chars = Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8);

var randomStr = new string(chars.SelectMany(str => str)
                                .OrderBy(c => Guid.NewGuid())
                                .Take(8).ToArray());

2
NewGuid在内部使用随机变量。因此,这仍然使用随机,只是将其隐藏。

2

这是Eric J解决方案的一种变体,即WinRT(Windows Store App)的加密声音:

public static string GenerateRandomString(int length)
{
    var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    var result = new StringBuilder(length);
    for (int i = 0; i < length; ++i)
    {
        result.Append(CryptographicBuffer.GenerateRandomNumber() % chars.Length);
    }
    return result.ToString();
}

如果性能很重要(尤其是长度较长时):

public static string GenerateRandomString(int length)
{
    var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    var result = new System.Text.StringBuilder(length);
    var bytes = CryptographicBuffer.GenerateRandom((uint)length * 4).ToArray();
    for (int i = 0; i < bytes.Length; i += 4)
    {
        result.Append(BitConverter.ToUInt32(bytes, i) % chars.Length);
    }
    return result.ToString();
}

1
从密码学上讲这不是正确的。由于模数运算未将ulong的整个宽度平均分布为62个字符,因此存在较小的偏差。
Lie Ryan

1

我知道这不是最好的方法。但是您可以尝试一下。

string str = Path.GetRandomFileName(); //This method returns a random file name of 11 characters
str = str.Replace(".","");
Console.WriteLine("Random string: " + str);

2
那条线怎么样?Console.WriteLine($“ Random string:{Path.GetRandomFileName()。Replace(”。“,”“)}”)); 是一行。
PmanAce

1

我不知道这在密码上听起来有多好,但是它比到目前为止(imo)更复杂的解决方案更具可读性和简洁性,它应该比System.Random基于基础的解决方案更“随机” 。

return alphabet
    .OrderBy(c => Guid.NewGuid())
    .Take(strLength)
    .Aggregate(
        new StringBuilder(),
        (builder, c) => builder.Append(c))
    .ToString();

我不确定我是否认为此版本或下一个版本“更漂亮”,但它们给出的结果完全相同:

return new string(alphabet
    .OrderBy(o => Guid.NewGuid())
    .Take(strLength)
    .ToArray());

当然,它并没有针对速度进行优化,因此,如果每秒生成数百万个随机字符串的任务至关重要,请尝试另一个!

注意:此解决方案不允许字母中的符号重复,并且字母的大小必须等于或大于输出字符串,这使得此方法在某些情况下不太理想,这完全取决于您的用例。


0

如果您的值不是完全随机的,但实际上可能取决于某些因素,则可以计算该“ somwthing”的md5或sha1哈希,然后将其截断为任意长度。

您也可以生成并截断Guid。


0
public static class StringHelper
{
    private static readonly Random random = new Random();

    private const int randomSymbolsDefaultCount = 8;
    private const string availableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    private static int randomSymbolsIndex = 0;

    public static string GetRandomSymbols()
    {
        return GetRandomSymbols(randomSymbolsDefaultCount);
    }

    public static string GetRandomSymbols(int count)
    {
        var index = randomSymbolsIndex;
        var result = new string(
            Enumerable.Repeat(availableChars, count)
                      .Select(s => {
                          index += random.Next(s.Length);
                          if (index >= s.Length)
                              index -= s.Length;
                          return s[index];
                      })
                      .ToArray());
        randomSymbolsIndex = index;
        return result;
    }
}

2
1)静态方法应该是线程安全的。2)增加索引而不random.Next直接使用直接结果有什么意义?使代码复杂,并且没有实现任何有用的功能。
CodesInChaos

0

这是一种无需定义字母和数字即可生成随机字母数字字符串(我用它来生成密码和测试数据)的机制,

CleanupBase64将删除字符串中的必要部分,并继续递归添加随机的字母数字字母。

        public static string GenerateRandomString(int length)
        {
            var numArray = new byte[length];
            new RNGCryptoServiceProvider().GetBytes(numArray);
            return CleanUpBase64String(Convert.ToBase64String(numArray), length);
        }

        private static string CleanUpBase64String(string input, int maxLength)
        {
            input = input.Replace("-", "");
            input = input.Replace("=", "");
            input = input.Replace("/", "");
            input = input.Replace("+", "");
            input = input.Replace(" ", "");
            while (input.Length < maxLength)
                input = input + GenerateRandomString(maxLength);
            return input.Length <= maxLength ?
                input.ToUpper() : //In my case I want capital letters
                input.ToUpper().Substring(0, maxLength);
        }

您已声明GenerateRandomString并拨打电话,以GetRandomString从内部SanitiseBase64String。您还可以声明SanitiseBase64String 和呼叫CleanUpBase64StringGenerateRandomString
李慧夏

0

有一个令人敬畏的nuget软件包使它变得如此简单。

var myObject = new Faker<MyObject>()
.RuleFor(p => p.MyAlphaNumericProperty, f => f.Random.AlphaNumeric(/*lenght*/ 7))
.Generate();

一个很好的例子是在这里


0

不是100%肯定,因为我没有在这里测试所有选项,但是在我测试过的选项中,这是最快的。用秒表计时,并显示9-10滴答,因此,如果速度比安全性更重要,请尝试以下操作:

 private static Random random = new Random(); 
 public static string Random(int length)
     {   
          var stringChars = new char[length];

          for (int i = 0; i < length; i++)
              {
                  stringChars[i] = (char)random.Next(0x30, 0x7a);                  
                  return new string(stringChars);
              }
     }

为什么要回复?这个问题已经回答了很多次,但你还是你的后半工作答案..
洛朗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.