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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1344221/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 15:24:37  来源:igfitidea点击:

How can I generate random alphanumeric strings?

c#.netrandom

提问by KingNestor

How can I generate a random 8 character alphanumeric string in C#?

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

采纳答案by dtb

I heard LINQ is the new black, so here's my attempt using LINQ:

我听说 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());
}

(Note: The use of the Randomclass makes this unsuitable for anything security related, such as creating passwords or tokens. Use the RNGCryptoServiceProviderclass if you need a strong random number generator.)

(注意:使用Random该类使其不适用于任何与安全相关的事情,例如创建密码或令牌。RNGCryptoServiceProvider如果您需要强大的随机数生成器,请使用该类。)

回答by Eric J.

UPDATED based on comments. The original implementation generated a-h ~1.95% of the time and the remaining characters ~1.56% of the time. The update generates all characters ~1.61% of the time.

FRAMEWORK SUPPORT - .NET Core 3 (and future platforms that support .NET Standard 2.1 or above) provides a cryptographically sound method RandomNumberGenerator.GetInt32()to generate a random integer within a desired range.

根据评论更新。原始实现生成 ah ~1.95% 的时间和剩余字符 ~1.56% 的时间。更新生成所有字符的概率约为 1.61%。

框架支持 - .NET Core 3(以及支持 .NET Standard 2.1 或更高版本的未来平台)提供了一种加密方法RandomNumberGenerator.GetInt32()来生成所需范围内的随机整数。

Unlike some of the alternatives presented, this one is cryptographically sound.

与提出的一些替代方案不同,这个方案在密码学上合理的

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

Based on a discussion of alternatives hereand updated/modified based on the comments below.

基于此处对替代方案的讨论,并根据以下评论进行更新/修改。

Here's a small test harness that demonstrates the distribution of characters in the old and updated output. For a deep discussion of the analysis of randomness, check out random.org.

这是一个小型测试工具,用于演示旧输出和更新输出中字符的分布。有关随机性分析的深入讨论,请访问 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")}%");
            }
        }
    }
}

回答by Dan Rigby

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

Not as elegant as the Linq solution.

不如 Linq 解决方案优雅。

(Note: The use of the Randomclass makes this unsuitable for anything security related, such as creating passwords or tokens. Use the RNGCryptoServiceProviderclass if you need a strong random number generator.)

(注意:使用Random该类使其不适用于任何与安全相关的事情,例如创建密码或令牌。RNGCryptoServiceProvider如果您需要强大的随机数生成器,请使用该类。)

回答by Adam Porad

Here's an example that I stole from Sam Allen example at Dot Net Perls

这是我从Dot Net Perls 的Sam Allen 示例中窃取的示例

If you only need 8 characters, then use Path.GetRandomFileName() in the System.IO namespace. Sam says using the "Path.GetRandomFileName method here is sometimes superior, because it uses RNGCryptoServiceProvider for better randomness. However, it is limited to 11 random characters."

如果您只需要 8 个字符,则在 System.IO 命名空间中使用 Path.GetRandomFileName()。Sam 说使用“这里的 Path.GetRandomFileName 方法有时更好,因为它使用 RNGCryptoServiceProvider 以获得更好的随机性。但是,它被限制为 11 个随机字符。”

GetRandomFileName always returns a 12 character string with a period at the 9th character. So you'll need to strip the period (since that's not random) and then take 8 characters from the string. Actually, you could just take the first 8 characters and not worry about the period.

GetRandomFileName 始终返回第 9 个字符处带有句点的 12 个字符的字符串。所以你需要去掉句号(因为这不是随机的),然后从字符串中取出 8 个字符。实际上,您可以只取前 8 个字符,而不必担心句号。

public string Get8CharacterRandomString()
{
    string path = Path.GetRandomFileName();
    path = path.Replace(".", ""); // Remove period.
    return path.Substring(0, 8);  // Return 8 character string
}

PS: thanks Sam

PS:谢谢山姆

回答by Douglas

Solution 1 - largest 'range' with most flexible length

解决方案 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);
    }
}

This solution has more range than using a GUID because a GUID has a couple of fixed bits that are always the same and therefore not random, for example the 13 character in hex is always "4" - at least in a version 6 GUID.

该解决方案比使用 GUID 的范围更大,因为 GUID 有几个固定位,这些位总是相同的,因此不是随机的,例如,十六进制中的 13 个字符总是“4”——至少在版本 6 GUID 中。

This solution also lets you generate a string of any length.

此解决方案还允许您生成任意长度的字符串。

Solution 2 - One line of code - good for up to 22 characters

解决方案 2 - 一行代码 - 适用于最多 22 个字符

Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 8);

You can't generate strings as long as Solution 1and the string doesn't have the same range due to fixed bits in GUID's, but in a lot of cases this will do the job.

只要解决方案 1 中的字符串不具有相同的范围,您就无法生成字符串,因为GUID 中的位是固定的,但在很多情况下,这可以完成工作。

Solution 3 - Slightly less code

解决方案 3 - 代码略少

Guid.NewGuid().ToString("n").Substring(0, 8);

Mostly keeping this here for historical purpose. It uses slightly less code, that though comes as the expense of having less range - because it uses hex instead of base64 it takes more characters to represent the same range compared the other solutions.

主要是出于历史目的将其保留在这里。它使用的代码略少,但代价是范围较小——因为它使用十六进制而不是 base64,与其他解决方案相比,它需要更多的字符来表示相同的范围。

Which means more chance of collision - testing it with 100,000 iterations of 8 character strings generated one duplicate.

这意味着更多的碰撞机会 - 用 8 个字符串的 100,000 次迭代测试它生成一个副本。

回答by james

Horrible, I know, but I just couldn't help myself:

可怕,我知道,但我就是忍不住:


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

回答by Merari Schroeder

After reviewing the other answers and considering CodeInChaos' comments, along with CodeInChaos still biased (although less) answer, I thought a final ultimate cut and paste solutionwas needed. So while updating my answer I decided to go all out.

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

For an up to date version of this code, please visit the new Hg repository on Bitbucket: https://bitbucket.org/merarischroeder/secureswiftrandom. I recommend you copy and paste the code from: https://bitbucket.org/merarischroeder/secureswiftrandom/src/6c14b874f34a3f6576b0213379ecdf0ffc7496ea/Code/Alivate.SolidSwiftRandom/SolidSwiftRandom.cs?at=default&fileviewer=file-view-default(make sure you click the Raw button to make it easier to copy and make sure you have the latest version, I think this link goes to a specific version of the code, not the latest).

有关此代码的最新版本,请访问 Bitbucket 上的新 Hg 存储库:https: //bitbucket.org/merarischroeder/secureswiftrandom。我建议你的代码复制并粘贴来自:https://bitbucket.org/merarischroeder/secureswiftrandom/src/6c14b874f34a3f6576b0213379ecdf0ffc7496ea/Code/Alivate.SolidSwiftRandom/SolidSwiftRandom.cs?at=default&fileviewer=file-view-default(请务必点击Raw 按钮以使其更易于复制并确保您拥有最新版本,我认为此链接指向代码的特定版本,而不是最新版本)。

Updated notes:

更新说明:

  1. Relating to some other answers - If you know the length of the output, you don't need a StringBuilder, and when using ToCharArray, this creates and fills the array (you don't need to create an empty array first)
  2. Relating to some other answers - You should use NextBytes, rather than getting one at a time for performance
  3. Technically you could pin the byte array for faster access.. it's usually worth it when your iterating more than 6-8 times over a byte array. (Not done here)
  4. Use of RNGCryptoServiceProvider for best randomness
  5. Use of caching of a 1MB buffer of random data- benchmarking shows cached single bytes access speed is ~1000x faster - taking 9ms over 1MB vs 989ms for uncached.
  6. Optimised rejection of bias zonewithin my new class.
  1. 与其他一些答案有关 - 如果您知道输出的长度,则不需要 StringBuilder,并且在使用 ToCharArray 时,这会创建并填充数组(您不需要先创建一个空数组)
  2. 关于其他一些答案 - 您应该使用 NextBytes,而不是一次获得一个以提高性能
  3. 从技术上讲,您可以固定字节数组以加快访问速度。当您在字节数组上迭代 6-8 次以上时,这通常是值得的。(这里不做)
  4. 使用RNGCryptoServiceProvider 以获得最佳随机性
  5. 使用缓存的 1MB 随机数据缓冲区- 基准测试显示缓存的单字节访问速度快约 1000 倍 - 超过 1MB 需要 9 毫秒,而未缓存的需要 989 毫秒。
  6. 在我的新课程中优化了对偏见区域的拒绝

End solution to question:

问题的最终解决方案:

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

But you need my new (untested) class:

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

/// <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
        }
    }
}

For history - my older solution for this answer, used Random object:

对于历史 - 我对此答案的旧解决方案,使用了 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);
    }

Performance:

表现:

  1. SecureFastRandom- First single run= ~9-33ms. Imperceptible. Ongoing: 5ms(sometimes it goes up to 13ms) over 10,000 iterations, With a single average iteration= 1.5 microseconds.. Note: Requires generally 2, but occasionally up to 8 cache refreshes - depends on how many single bytes exceed the bias zone
  2. Random- First single run= ~0-1ms. Imperceptible. Ongoing: 5msover 10,000 iterations. With a single average iteration= .5 microseconds.. About the same speed.
  1. SecureFastRandom-第一次单次运行= ~9-33ms。难以察觉。正在进行:5毫秒(有时会达到 13毫秒)超过 10,000 次迭代,单次平均迭代= 1.5 微秒。. 注意:通常需要 2 次,但偶尔最多需要 8 次缓存刷新 - 取决于有多少单个字节超出偏差区域
  2. 随机-第一次单次运行= ~0-1ms。难以察觉。正在进行:5毫秒超过 10,000 次迭代。单次平均迭代= 0.5 微秒。. 速度差不多。

Also check out:

还请查看:

These links are another approach. Buffering could be added to this new code base, but most important was exploring different approaches to removing bias, and benchmarking the speeds and pros/cons.

这些链接是另一种方法。缓冲可以添加到这个新的代码库中,但最重要的是探索不同的方法来消除偏差,并对速度和优缺点进行基准测试。

回答by Alexey B.

If your values are not completely random, but in fact may depend on something - you may compute an md5 or sha1 hash of that 'somwthing' and then truncate it to whatever length you want.

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

Also you may generate and truncate a guid.

您也可以生成和截断一个 guid。

回答by AAD

Another option could be to use Linq and aggregate random chars into a stringbuilder.

另一种选择是使用 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());

回答by CodesInChaos

The main goals of my code are:

我的代码的主要目标是:

  1. The distribution of strings is almost uniform (don't care about minor deviations, as long as they're small)
  2. It outputs more than a few billion strings for each argument set. Generating an 8 character string (~47 bits of entropy) is meaningless if your PRNG only generates 2 billion (31 bits of entropy) different values.
  3. It's secure, since I expect people to use this for passwords or other security tokens.
  1. 字符串的分布几乎是均匀的(不关心微小的偏差,只要它们很小)
  2. 它为每个参数集输出超过几十亿个字符串。如果您的 PRNG 仅生成 20 亿(31 位熵)不同的值,则生成 8 个字符串(约 47 位熵)是没有意义的。
  3. 它是安全的,因为我希望人们将其用于密码或其他安全令牌。

The first property is achieved by taking a 64 bit value modulo the alphabet size. For small alphabets (such as the 62 characters from the question) this leads to negligible bias. The second and third property are achieved by using RNGCryptoServiceProviderinstead of System.Random.

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