33

I was wondering how I can generate a strong and secure password in C#.

I googled a little bit and saw this formula in Wikipedia, where L is the length of the password and N is the number of possible symbols:

alt text

Also, I've found this question, but for some reason the method Membership.GeneratePassword just returns a random number with 1 digit, which absolutely no password. All the rest solutions, were very slow (>= 0.5 secs).

I need help implementing this formula (I don't know where to start). You may also suggest another solution or explain why the GeneratePassword isn't working.

Community
  • 1
  • 1
Alon Gubkin
  • 56,458
  • 54
  • 195
  • 288

8 Answers8

86

I just tried the following in linqpad:

System.Web.Security.Membership.GeneratePassword(25, 10)

This is the password I got:

[XTJ_67g.i/ag1rL)6_Yv>*+%

Or, if that's not secure enough, try this:

System.Web.Security.Membership.GeneratePassword(128, 100)

which got me the following when running it three times:

|c^.:?m)#q+(]V;}[Z(})/?-;$]+@!|^/8*_9.$&.&!(?=^!Wx?[@%+&-@b;)>N;&+*w[>$2+_$%l;+h+#zhs^{e?&=*(}X_%|:}]]}*X[+)Er%J/-=;Q0{:+=%c7:^$

/:_)hxF+*){2|;(>:*N^+!_&|}B.$})?[V=[+v({-:-@9-Z$j?.[-}(@MHx+}(}Mz_S(7#4}{..>@G|!+++{+C=|_}=+r^@&$0;L*|kz-;$++/N3$=}?;%&]]*/^#^!+

:*{]-x^$g{|?*))_=B@^.#%L;g|+)#[nq}?y(_(m;]S^I$*q=l-[_/?}&-!k^(+[_{Z|&:^%!_)!=p%=)=wYd-#.UP$%s1{*l%+[%?!c+7=@=.;{+M)!^}&d/]{];(&}

this took way less than a second, btw. The framework is your friend.

See http://msdn.microsoft.com/en-us/library/system.web.security.membership.generatepassword.aspx

Çağdaş Tekin
  • 16,592
  • 4
  • 49
  • 58
  • Please note the editor doesn't play friendly with some of the characters in the password. –  Feb 15 '10 at 15:08
  • As I said, this is a great method but is just returns a single digit in my computer, and in many other computers. – Alon Gubkin Feb 15 '10 at 15:08
  • 4
    @alon you're doing it wrong. Trust me. This method works and is bulletproof on any .NET installation on any computer. –  Feb 15 '10 at 15:10
  • @AlonGubkin Are you passing in 1 as the first param value? – billpg Apr 16 '12 at 09:35
  • @billpg: You know, this question is over two years old, and is answered, right? –  Apr 16 '12 at 12:44
  • 2
    @Will - I wouldn't call "you're doing it wrong" much of an answer. :) – billpg Apr 16 '12 at 17:06
  • 1
    @billpg: OP did! And I think its an *awesome* answer full of virtue and wisdom. So there. –  Apr 16 '12 at 17:14
  • 1
    Is there an alternative not dependent on the System.Web... namespace? – CRice Apr 19 '12 at 00:39
  • 3
    @CRice I think that `Membership.GeneratePassword` is based on `System.Security.Cryptography.RNGCryptoServiceProvider`, so you can implement your own GeneratePassword with that. Here is an example: http://www.obviex.com/Samples/Password.aspx – Alon Gubkin Apr 19 '12 at 02:05
  • Thanks, I found a decent alternative :) – CRice May 17 '12 at 11:46
  • 4
    While this is strong in the maths sense - it completely overlooks the human element. Any password in this format is going to have to be written down or stored which opens a whole heap more issues. Better to use a pass phrase "HelpMeObiWanKanobieYou'reMyOnlyHope" ... – AndyM Jun 06 '12 at 03:18
  • 5
    "This method works and is bulletproof on any .NET installation" Except the assembly isn't available with .NET Core and isn't part of .NET Standard.. – Adam Williams Nov 16 '19 at 14:21
  • ther is no System.Security.Cryptography.RNGCryptoServiceProvider.Membership nor System.Web.Security – phil123456 Dec 10 '20 at 09:07
  • @AdamWilliams you'll have to forgive user1228 for not being able to see the future since .net core and .net standard did not exist back then... – Keith Banner Jun 19 '23 at 20:41
  • Old answers that are based on legacy .NET should be downvoted, commented on and buried as appropriate. Times change, and an answer that was useful back in 2010 may be completely useless in 2023. StackOverflow should be timeless, and answers revised over time to be relevant to today's technology. – Adam Williams Jun 22 '23 at 11:57
  • @Adam Williams most absurd commentary I've read here in my entire life. For that rule of three, let's burn and forget all questions and solutions about any technology that has a better replacement today, like those shitty codes from Batch-Script or from VBS or from VB6 and even from C/C++ considering it exists the modern VC++, right?. My god. Let's burn and hide all the entire .NET framework class library because it just exists .NET Core and everybody use it (irony). – ElektroStudios Jul 09 '23 at 02:06
16

Not sure where I found this but here's a class to generate high entropy, truly random strings that can be used as passwords.

using System.Security.Cryptography;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class PasswordGenerator
{
    public int MinimumLengthPassword { get; private set; }
    public int MaximumLengthPassword { get; private set; }
    public int MinimumLowerCaseChars { get; private set; }
    public int MinimumUpperCaseChars { get; private set; }
    public int MinimumNumericChars { get; private set; }
    public int MinimumSpecialChars { get; private set; }

    public static string AllLowerCaseChars { get; private set; }
    public static string AllUpperCaseChars { get; private set; }
    public static string AllNumericChars { get; private set; }
    public static string AllSpecialChars { get; private set; }
    private readonly string _allAvailableChars;

    private readonly RandomSecureVersion _randomSecure = new RandomSecureVersion();
    private int _minimumNumberOfChars;

    static PasswordGenerator()
    {
        // Define characters that are valid and reject ambiguous characters such as ilo, IO and 1 or 0
        AllLowerCaseChars = GetCharRange('a', 'z', exclusiveChars: "ilo");
        AllUpperCaseChars = GetCharRange('A', 'Z', exclusiveChars: "IO");
        AllNumericChars = GetCharRange('2', '9');
        AllSpecialChars = "!@#%*()$?+-=";

    }

    public PasswordGenerator(
        int minimumLengthPassword = 15,
        int maximumLengthPassword = 20,
        int minimumLowerCaseChars = 2,
        int minimumUpperCaseChars = 2,
        int minimumNumericChars = 2,
        int minimumSpecialChars = 2)
    {
        if (minimumLengthPassword < 15)
        {
            throw new ArgumentException("The minimumlength is smaller than 15.",
                "minimumLengthPassword");
        }

        if (minimumLengthPassword > maximumLengthPassword)
        {
            throw new ArgumentException("The minimumLength is bigger than the maximum length.",
                "minimumLengthPassword");
        }

        if (minimumLowerCaseChars < 2)
        {
            throw new ArgumentException("The minimumLowerCase is smaller than 2.",
                "minimumLowerCaseChars");
        }

        if (minimumUpperCaseChars < 2)
        {
            throw new ArgumentException("The minimumUpperCase is smaller than 2.",
                "minimumUpperCaseChars");
        }

        if (minimumNumericChars < 2)
        {
            throw new ArgumentException("The minimumNumeric is smaller than 2.",
                "minimumNumericChars");
        }

        if (minimumSpecialChars < 2)
        {
            throw new ArgumentException("The minimumSpecial is smaller than 2.",
                "minimumSpecialChars");
        }

        _minimumNumberOfChars = minimumLowerCaseChars + minimumUpperCaseChars +
                                minimumNumericChars + minimumSpecialChars;

        if (minimumLengthPassword < _minimumNumberOfChars)
        {
            throw new ArgumentException(
                "The minimum length of the password is smaller than the sum " +
                "of the minimum characters of all catagories.",
                "maximumLengthPassword");
        }

        MinimumLengthPassword = minimumLengthPassword;
        MaximumLengthPassword = maximumLengthPassword;

        MinimumLowerCaseChars = minimumLowerCaseChars;
        MinimumUpperCaseChars = minimumUpperCaseChars;
        MinimumNumericChars = minimumNumericChars;
        MinimumSpecialChars = minimumSpecialChars;

        _allAvailableChars =
            OnlyIfOneCharIsRequired(minimumLowerCaseChars, AllLowerCaseChars) +
            OnlyIfOneCharIsRequired(minimumUpperCaseChars, AllUpperCaseChars) +
            OnlyIfOneCharIsRequired(minimumNumericChars, AllNumericChars) +
            OnlyIfOneCharIsRequired(minimumSpecialChars, AllSpecialChars);
    }

    private string OnlyIfOneCharIsRequired(int minimum, string allChars)
    {
        return minimum > 0 || _minimumNumberOfChars == 0 ? allChars : string.Empty;
    }

    public string Generate()
    {
        var lengthOfPassword = _randomSecure.Next(MinimumLengthPassword, MaximumLengthPassword);

        // Get the required number of characters of each catagory and 
        // add random charactes of all catagories
        var minimumChars = GetRandomString(AllLowerCaseChars, MinimumLowerCaseChars) +
                        GetRandomString(AllUpperCaseChars, MinimumUpperCaseChars) +
                        GetRandomString(AllNumericChars, MinimumNumericChars) +
                        GetRandomString(AllSpecialChars, MinimumSpecialChars);
        var rest = GetRandomString(_allAvailableChars, lengthOfPassword - minimumChars.Length);
        var unshuffeledResult = minimumChars + rest;

        // Shuffle the result so the order of the characters are unpredictable
        var result = unshuffeledResult.ShuffleTextSecure();
        return result;
    }

    private string GetRandomString(string possibleChars, int lenght)
    {
        var result = string.Empty;
        for (var position = 0; position < lenght; position++)
        {
            var index = _randomSecure.Next(possibleChars.Length);
            result += possibleChars[index];
        }
        return result;
    }

    private static string GetCharRange(char minimum, char maximum, string exclusiveChars = "")
    {
        var result = string.Empty;
        for (char value = minimum; value <= maximum; value++)
        {
            result += value;
        }
        if (!string.IsNullOrEmpty(exclusiveChars))
        {
            var inclusiveChars = result.Except(exclusiveChars).ToArray();
            result = new string(inclusiveChars);
        }
        return result;
    }
}

internal static class Extensions
{
    private static readonly Lazy<RandomSecureVersion> RandomSecure =
        new Lazy<RandomSecureVersion>(() => new RandomSecureVersion());
    public static IEnumerable<T> ShuffleSecure<T>(this IEnumerable<T> source)
    {
        var sourceArray = source.ToArray();
        for (int counter = 0; counter < sourceArray.Length; counter++)
        {
            int randomIndex = RandomSecure.Value.Next(counter, sourceArray.Length);
            yield return sourceArray[randomIndex];

            sourceArray[randomIndex] = sourceArray[counter];
        }
    }

    public static string ShuffleTextSecure(this string source)
    {
        var shuffeldChars = source.ShuffleSecure().ToArray();
        return new string(shuffeldChars);
    }
}

internal class RandomSecureVersion
{
    //Never ever ever never use Random() in the generation of anything that requires true security/randomness
    //and high entropy or I will hunt you down with a pitchfork!! Only RNGCryptoServiceProvider() is safe.
    private readonly RNGCryptoServiceProvider _rngProvider = new RNGCryptoServiceProvider();

    public int Next()
    {
        var randomBuffer = new byte[4];
        _rngProvider.GetBytes(randomBuffer);
        var result = BitConverter.ToInt32(randomBuffer, 0);
        return result;
    }

    public int Next(int maximumValue)
    {
        // Do not use Next() % maximumValue because the distribution is not OK
        return Next(0, maximumValue);
    }

    public int Next(int minimumValue, int maximumValue)
    {
        var seed = Next();

        //  Generate uniformly distributed random integers within a given range.
        return new Random(seed).Next(minimumValue, maximumValue);
    }
}

Consume in your code thusly:

var generator = new PasswordGenerator();
string password = generator.Generate();
Console.WriteLine(password);
Richard
  • 286
  • 3
  • 9
10

To address your question about that formula:

The formula is saying that a password of length L drawn from an alphabet of N symbols is equivalent to a password of length H drawn from an alphabet of two symbols. So if you have, say, 64 symbols (say abc...xyzABC...XYZ01...89_!) and the password is 10 characters long, then that gives you equivalent security to a password 10 log2 64 = 60 characters long drawn from the alphabet "ab".

A "log" is the inverse operation of exponentiation. Two to the sixth power gives you sixty-four, therefore the "log two" of sixty-four gives you six.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
5

I don't know if this will help you, but this is what I use when I want to generate a random password which is also strong. It's quick and simple to implement/understand and isn't as much of an overkill as the one through the membership provider above...

    private string Token(byte Length) {
        char[] Chars = new char[] {
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
        };
        string String = string.Empty;
        Random Random = new Random();

        for (byte a = 0; a < Length; a++) {
            String += Chars[Random.Next(0, 61)];
        };

        return (String);
    }
Gup3rSuR4c
  • 9,145
  • 10
  • 68
  • 126
  • 8
    `System.Random` should never be used for generating passwords its more predictable and isn't intended for that purpose. Instead use `System.Security.Cryptography.RNGCryptoServiceProvider` See http://stackoverflow.com/a/411985/1160036 and http://stackoverflow.com/a/8996788/1160036 – Despertar Feb 28 '13 at 04:08
  • 1
    I would not recommend roll-your-own for security design. Using a built-in security framework like .net's will gives you thoroughly tested code thought-through by cryptogrophy experts. It's easy to make a lot of mistakes on your own. – Jordan Morris May 18 '13 at 11:16
  • I like the comment in the code from Richard's answer: `Never ever ever never use Random() in the generation of anything that requires true security/randomness and high entropy or I will hunt you down with a pitchfork!! Only RNGCryptoServiceProvider() is safe.` – Darkproduct Sep 21 '22 at 13:07
  • I agree with those who commented before me. And one more thing: because of Random.Next(0,61) there will never be '9' in the generated string. Regarding to the definition of Random.Next the generated number is always less then 61. Better to use Random.Next(0,Chars.Length) – 75ntamas Jul 14 '23 at 06:49
2

I used random string from characters like below

public static string RandomString(int length)
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&?%$@";
        return new string(Enumerable.Repeat(chars, length)
          .Select(s => s[Random.Next(s.Length)]).ToArray());
    }
1

Why not just fill an array with some characters and pick on random a number of them. You can divide them in groups to be sure that are include letters numbers and special characters.

You will also have to pick a proper length and how much of every group of characters to include and that's it. I don't think you need some sophisticated formulas.

anthares
  • 11,070
  • 4
  • 41
  • 61
  • 1
    Nothing is... But if you limit your logins to 5 logins per hour say you reduce the risk of the brute force attack. – LiamB Feb 15 '10 at 15:03
  • 2
    What could be harder than picking random chars ? – anthares Feb 15 '10 at 15:03
  • @anthares I agree, Alon whats this for? – LiamB Feb 15 '10 at 15:04
  • Nope, I'm not going to limit my logins per hour, it is very irrating to the user. Instead, I'm going to put CAPTCHA after 3 tries. – Alon Gubkin Feb 15 '10 at 15:05
  • @Alon that method will work in the same way. So brite force attacks have been restricted. – LiamB Feb 15 '10 at 15:08
  • @Alon, after reading all of these I am curious why you need something so overkill? In my applications, which deal with a ton of personal customer information, I just use the password generator I posted below with varying length. Its nice to see you're interested in securing your application, but you have to keep ease of use in mind as well. Ultimately if someone wants to hack you, they'll manage to do it somehow no matter how strong a password you have. – Gup3rSuR4c Feb 21 '10 at 20:01
1

For systems that don't allow user-generated passwords it's very easy, actually: Any password is as secure as it's long. Not counting, of course, people who tack post-its to monitors, etc.

You probably want to maximize the set of characters from which the password is generated. But restricting the generated passwords greatly reduces the search space and therefore makes the password less secure. Again, this only holds if the user can't choose their own password.

If you deal with both generated and user-created passwords, then all bets are off, obviously. You then probably want to generate the passwords in a way that it uses as many characters from different classes as possible, resembling a strong user-chosen password. Ideally it should conform to the same constraints that the user-created password has to pass as well (if any).

Joey
  • 344,408
  • 85
  • 689
  • 683
0

The following is pretty quick and works well.

  1. Random special characters for the required length divided by 4
  2. Random lowercase characters for the required length divided by 3
  3. Random uppercase characters for the required length divided by 3
  4. Random numerical characters for the required length divided by 2
  5. Concatenate the 4 random arrays
  6. Take random characters from the concatenated array for the required length
  7. Return the new string

Note the Random is a global static variable.

public static Random random = new Random();

public static string RandomString(int length)
{
    var specialLength = (int)Math.Ceiling(length / 4d);
    var lowerUpperLength = (int)Math.Ceiling(length / 3d);
    var numericLength = (int)Math.Ceiling(length / 2d);

    var special = Enumerable.Repeat("!@#$%^&|+-.,?", specialLength)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    var lower = Enumerable.Repeat("abcdefghijklmnopqrstuvwxyz", lowerUpperLength)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    var upper = Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", lowerUpperLength)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    var numeric = Enumerable.Repeat("0123456789", numericLength)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    
    var scrambledConcat = special.Concat(lower)
        .Concat(upper).Concat(numeric)
        .ToArray();
    
    var scrambledChars = Enumerable.Repeat(scrambledConcat, length)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    
    return new string(scrambledChars);
}

Output 5X examples for a length of 6:

@n.c7c

&T,T0S

2b$9$H

AEEcE0

6x6VB3

Output 5X examples for a length of 20:

41@8?@K4@y@uSu$K31zS

L750T01T#00A17Tq5O+^

%#z#@BEBEbG4xU2AUx26

Ry90j4RyW6.VBRV0-60!

Z84SJ!t075%7a8!nn84M

Output 5X examples for a length of 100:

!9i7vz297Dlq$ffL35qzi4j63UJ9Nv53^9K@6-t6N17@jy73@VK80^y423H1bLiaHx0q9Ba5b?&9@4154lo?$6@e5L9e6-B00X63

-Ykk9%Mt08Ky4TW426rI53k12F#z6G8WWuR0|Bh?w,mY4XkU2eA8%Wz565to5m42Z6|84%UF-^8N3Uv#$72#d65#BkNtcn%3i8M.

f58vaQg0@VB6sgXeChud@^8f1e63q3e@68ep0d6Eg,861Xq8@dgBZfs2L3d3@7$djZZ232Q&V6q?psX$VB6tSsqZ3HVZk67qA06q

&uZecy^nA?2m4a#&M4?M9%hj7d1dmPUqlIiUaj4Z4zKZInG33uQk161sD4?m4e3B^mKu22n0h1uPJ&#97UQ^ys519^XE1,9UE+4J

4%q,B#0Vi5%BjfG@43Mo^k6+24P3ek#32L94s3%!Yki3^53%iCmP,.K%4+m.bw06K,JfU4e2P8WJ0^%40%^K150iz44-0fn?U8L0

Feel free to add or remove characters in the strings.

Pierre
  • 8,397
  • 4
  • 64
  • 80