46

Is there any alternative to System.Web.Security.Membership.GeneratePassword in AspNetCore (netcoreapp1.0).

The easiest way would be to just use a Guid.NewGuid().ToString("n") which is long enough to be worthy of a password but it's not fully random.

Athari
  • 33,702
  • 16
  • 105
  • 146
Ovi
  • 2,620
  • 7
  • 34
  • 51
  • 2
    What does `System.Web.Security.Membership.GeneratePassword` do that you don't want? – Benjamin Soulier Aug 17 '16 at 11:30
  • 3
    All that method does is [generate a random password](https://msdn.microsoft.com/en-us/library/system.web.security.membership.generatepassword(v=vs.110).aspx), that super easy to do yourself. – DavidG Aug 17 '16 at 11:30

2 Answers2

92

Here's a class/method, based on the source of Membership.GeneratePassword of that works on .NET Core:

public static class Password
{
    private static readonly char[] Punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray();

    public static string Generate(int length, int numberOfNonAlphanumericCharacters)
    {
        if (length < 1 || length > 128)
        {
            throw new ArgumentException(nameof(length));
        }

        if (numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0)
        {
            throw new ArgumentException(nameof(numberOfNonAlphanumericCharacters));
        }

        using (var rng = RandomNumberGenerator.Create())
        {
            var byteBuffer = new byte[length];

            rng.GetBytes(byteBuffer);

            var count = 0;
            var characterBuffer = new char[length];

            for (var iter = 0; iter < length; iter++)
            {
                var i = byteBuffer[iter] % 87;

                if (i < 10)
                {
                    characterBuffer[iter] = (char)('0' + i);
                }
                else if (i < 36)
                {
                    characterBuffer[iter] = (char)('A' + i - 10);
                }
                else if (i < 62)
                {
                    characterBuffer[iter] = (char)('a' + i - 36);
                }
                else
                {
                    characterBuffer[iter] = Punctuations[i - 62];
                    count++;
                }
            }

            if (count >= numberOfNonAlphanumericCharacters)
            {
                return new string(characterBuffer);
            }

            int j;
            var rand = new Random();

            for (j = 0; j < numberOfNonAlphanumericCharacters - count; j++)
            {
                int k;
                do
                {
                    k = rand.Next(0, length);
                }
                while (!char.IsLetterOrDigit(characterBuffer[k]));

                characterBuffer[k] = Punctuations[rand.Next(0, Punctuations.Length)];
            }

            return new string(characterBuffer);
        }
    }
}

I've omitted the do...while loop over the CrossSiteScriptingValidation.IsDangerousString. You can add that back in yourself if you need it.

You use it like this:

var password = Password.Generate(32, 12);

Also, make sure you reference System.Security.Cryptography.Algorithms.

khellang
  • 17,550
  • 6
  • 64
  • 84
  • This was very helpful. Is there some neat way to add the rest of nonalphanumeric characters, [cause you omitted some ;)]? Creating StringRaw class or something? – Tommy Oct 25 '17 at 13:49
  • 1
    This method (as the one in the framework) has a bias due to the use of the modulo in `byte % 87`. Might not be a problem for most use cases, but something to keep in mind. Bias changes if characters are added to or removed from the `Punctuations` array. – linac Aug 07 '19 at 15:00
  • This will throw, with the punctuation list in this length, if `i == 86` as you will get an out of index error. – Razze Sep 16 '21 at 08:58
  • @Razze Hah, Microsoft probably never got the bug report because (I'm assuming) very few people generate passwords longer than 85 characters :P – khellang Sep 17 '21 at 13:09
  • `i` is not connected to the password length, but to the length of your special signs. We ended up doing something like `var i = byteBuffer[iter] % Punctuations.length + 62;` – Razze Sep 18 '21 at 14:04
  • I'm wondering if Rene's answer below should be merged into khellangs answer? – Chris Nevill Aug 09 '22 at 15:25
1

System.Random doesn't provide enough entropy when used for security reasons.

https://cwe.mitre.org/data/definitions/331.html

Why use the C# class System.Random at all instead of System.Security.Cryptography.RandomNumberGenerator?

Please see the example below for a more secure version of @khellang version

    public static class Password
    {
        private static readonly char[] Punctuations = "!@#$%^&*()_-+[{]}:>|/?".ToCharArray();
        public static string Generate(int length, int numberOfNonAlphanumericCharacters)
        {
            if (length < 1 || length > 128)
            {
                throw new ArgumentException("length");
            }

            if (numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0)
            {
                throw new ArgumentException("numberOfNonAlphanumericCharacters");
            }

            using (var rng = RandomNumberGenerator.Create())
            {
                var byteBuffer = new byte[length];
               
                rng.GetBytes(byteBuffer);

                var count = 0;
                var characterBuffer = new char[length];

                for (var iter = 0; iter < length; iter++)
                {
                    var i = byteBuffer[iter] % 87;

                    if (i < 10)
                    {
                        characterBuffer[iter] = (char)('0' + i);
                    }
                    else if (i < 36)
                    {
                        characterBuffer[iter] = (char)('A' + i - 10);
                    }
                    else if (i < 62)
                    {
                        characterBuffer[iter] = (char)('a' + i - 36);
                    }
                    else
                    {
                        characterBuffer[iter] = Punctuations[GetRandomInt(rng, Punctuations.Length)];
                        count++;
                    }
                }

                if (count >= numberOfNonAlphanumericCharacters)
                {
                    return new string(characterBuffer);
                }

                int j;
                
                for (j = 0; j < numberOfNonAlphanumericCharacters - count; j++)
                {
                    int k;
                    do
                    {
                        k = GetRandomInt(rng, length);
                    }
                    while (!char.IsLetterOrDigit(characterBuffer[k]));

                    characterBuffer[k] = Punctuations[GetRandomInt(rng, Punctuations.Length)];
                }

                return new string(characterBuffer);
            }
        }

        private static int GetRandomInt(RandomNumberGenerator randomGenerator)
        {
            var buffer = new byte[4];
            randomGenerator.GetBytes(buffer);

            return BitConverter.ToInt32(buffer);
        }
        private static int GetRandomInt(RandomNumberGenerator randomGenerator, int maxInput)
        {
            return Math.Abs(GetRandomInt(randomGenerator) % maxInput);
        }
    }
ReneLombard
  • 46
  • 1
  • 4