How can I generate random alphanumeric strings?

后端 未结 30 2809
予麋鹿
予麋鹿 2020-11-22 03:17

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

相关标签:
30条回答
  • 2020-11-22 03:43

    Just some performance comparisons of the various answers in this thread:

    Methods & Setup

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

    Results

    Tested in LinqPad. For string size of 10, generates:

    • from Linq = chdgmevhcy [10]
    • from Loop = gtnoaryhxr [10]
    • from Select = rsndbztyby [10]
    • from GenerateRandomString = owyefjjakj [10]
    • from SecureFastRandom = VzougLYHYP [10]
    • from SecureFastRandom-NoCache = oVQXNGmO1S [10]

    And the performance numbers tend to vary slightly, very occasionally NonOptimized is actually faster, and sometimes ForLoop and GenerateRandomString switch who's in the lead.

    • LinqIsTheNewBlack (10000x) = 96762 ticks elapsed (9.6762 ms)
    • ForLoop (10000x) = 28970 ticks elapsed (2.897 ms)
    • ForLoopNonOptimized (10000x) = 33336 ticks elapsed (3.3336 ms)
    • Repeat (10000x) = 78547 ticks elapsed (7.8547 ms)
    • GenerateRandomString (10000x) = 27416 ticks elapsed (2.7416 ms)
    • SecureFastRandom (10000x) = 13176 ticks elapsed (5ms) lowest [Different machine]
    • SecureFastRandom-NoCache (10000x) = 39541 ticks elapsed (17ms) lowest [Different machine]
    0 讨论(0)
  • 2020-11-22 03:43

    We also use custom string random but we implemented is as a string's helper so it provides some flexibility...

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

    Usage

    var random = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Random();
    

    or

    var random = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".Random(16);
    
    0 讨论(0)
  • 2020-11-22 03:44

    One line of code Membership.GeneratePassword() does the trick :)

    Here is a demo for the same.

    0 讨论(0)
  • 2020-11-22 03:45

    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 solution was needed. So while updating my answer I decided to go all out.

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

    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 zone within my new class.

    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:

        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: 5ms over 10,000 iterations. With a single average iteration= .5 microseconds.. About the same speed.

    Also check out:

    • https://bitbucket.org/merarischroeder/number-range-with-no-bias/src
    • https://stackoverflow.com/a/45118325/887092

    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.

    0 讨论(0)
  • 2020-11-22 03:46

    Try to combine two parts: unique (sequence, counter or date ) and random

    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;
    
            using (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)
        {
            const string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //if you wish to make the 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;
        }
    }
    

    Tests:

        [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())
        }
    
    0 讨论(0)
  • 2020-11-22 03:46

    Here is a variant of Eric J's solution, i.e. cryptographically sound, for 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();
    }
    

    If performance matters (especially when length is high):

    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();
    }
    
    0 讨论(0)
提交回复
热议问题