Yesterday I had to implement a C# method that creates a random generated password in C#. Before committing into it I spent some minutes surfing the web, trying to find something I could use. I stumbled upon this 2006 post from Mads Kristensen, which is a guy I seriously love for all the great work he did with some incredibly useful Visual Studio extensions such as Web Essentials, Web Compiler, ASP.NET Core Web Templates - and a bunch of other great stuff.
However, the function I found in that post didn't help me much, because it had no way to ensure any strong-password requisite other than the minimum required length: more specifically, I need to generate password with at least one uppercase & lowercase letter, digit and non-alphanumeric character - and also a certain amount of unique characters. The random password generated against the Mads function could have them or not, depending on the randomness: that simply won't do in my scenario, since I had to deal with the UserManager.CreateUserAsync(username, password) method of the Microsoft.AspNetCore.Identity namespace, which utterly crashes whenever the password isn't strong enough.
Right after Mads, I found this neat StackOverflow thread where the community users enumerated a number of available options, including:
- Using the Membership.GeneratePassword method from the System.Web.Security namespace, which sadly isnt' available in ASP.NET Core.
- Creating a ASP.NET Core port of the above method based on the official source code (licensed under MIT).
- Implement their own method.
However, none of these methods really helped me. Eventually, I ended up coding my own helper class - just like Mads Kristensen more than 11 years ago:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/// <summary> /// Generates a Random Password /// respecting the given strength requirements. /// </summary> /// <param name="opts">A valid PasswordOptions object /// containing the password strength requirements.</param> /// <returns>A random password</returns> public static string GenerateRandomPassword(PasswordOptions opts = null) { if (opts == null) opts = new PasswordOptions() { RequiredLength = 8, RequiredUniqueChars = 4, RequireDigit = true, RequireLowercase = true, RequireNonAlphanumeric = true, RequireUppercase = true }; string[] randomChars = new [] { "ABCDEFGHJKLMNOPQRSTUVWXYZ", // uppercase "abcdefghijkmnopqrstuvwxyz", // lowercase "0123456789", // digits "!@$?_-" // non-alphanumeric }; CryptoRandom rand = new CryptoRandom(); List<char> chars = new List<char>(); if (opts.RequireUppercase) chars.Insert(rand.Next(0, chars.Count), randomChars[0][rand.Next(0, randomChars[0].Length)]); if (opts.RequireLowercase) chars.Insert(rand.Next(0, chars.Count), randomChars[1][rand.Next(0, randomChars[1].Length)]); if (opts.RequireDigit) chars.Insert(rand.Next(0, chars.Count), randomChars[2][rand.Next(0, randomChars[2].Length)]); if (opts.RequireNonAlphanumeric) chars.Insert(rand.Next(0, chars.Count), randomChars[3][rand.Next(0, randomChars[3].Length)]); for (int i = chars.Count; i < opts.RequiredLength || chars.Distinct().Count() < opts.RequiredUniqueChars; i++) { string rcs = randomChars[rand.Next(0, randomChars.Length)]; chars.Insert(rand.Next(0, chars.Count), rcs[rand.Next(0, rcs.Length)]); } return new string(chars.ToArray()); } |
As you can see, it takes a PasswordOptions object as parameter, which is shipped by the Microsoft.AspNetCore.Identity assembly, but you can easily replace it with a two int - four bool parameter group or POCO class if you don't have that package installed. In the likely case you have it in your ASP.NET Core project, you can use the exact same object used in the ConfigureService method of the Startup class when defining the password requirements:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[...] // Add ASP.NET Identity support services.AddIdentity<ApplicationUser, IdentityRole>( opts => { opts.Password.RequireDigit = true; opts.Password.RequireLowercase = true; opts.Password.RequireUppercase = true; opts.Password.RequireNonAlphanumeric = false; opts.Password.RequiredLength = 8; }) .AddEntityFrameworkStores<ApplicationDbContext>(); [...] |
Since July 2018 this library is also available on GitHub under Apache License 2.0: be sure to check that repository to retrieve the latest version!
Security considerations
The password randomness is calculated using a secure CryptoRandom class (adapted from the CryptoRandom class used within the IdentityModel) that mimics the standard Random class in the .NET Framework, replacing its standard (non-secure) behaviour with a cryptographic random number generator: you can find it here.
In the GitHub project you'll also find an alternative CryptoRandom implementation (CryptoRandom2), which I've taken from here (credits to Stephen Toub, Shawn Farkas and Markus Olsson). If you want to use the alternative implementation, just rename the CryptoRandom2.cs file and class to CryptoRandom, replacing the previous one - or just instantiate a CryptoRandom2 object in the PasswordGenerator.cs file.
Conclusions
That's it for now: hope you'll like the helper method!
P.S.: If you need a C# helper function to check for strong passwords, don't forget to also read this post.
There are other good suggestions in the same SO thread https://stackoverflow.com/questions/54991/generating-random-passwords , e.g http://www.obviex.com/Samples/Password.aspx and https://www.siepman.nl/blog/post/2014/05/31/Random-password-generator-with-numbers-and-special-characters.aspx
Have you considered them before writing your own implementation ?
Actually, I did :) However, although working perfectly fine, both of them lacked some of the features I was looking for, such as:
I would’ve rather extended the 2nd link (which is really awesome) adding my additional feature, but I needed something more compact because I needed to put that generator class in a book I was writing at the time ( ASP.NET Core 2 and Angular 5) and I didn’t want to bother the reader with too much source code.