Since working with ASP.Net 2.0, I’ve been using the included Membership API almost exclusively. There was one piece of it I was never crazy about: the autogenerated passwords created by the SqlMembershipProvider (and, presumably, other providers).
Let me rephrase that. I didn’t have a problem with the auto-generated passwords, which included many symbol characters. For me, it was simple enough to copy/paste the autogenerated password (which would look something like this: @&#eyue@^%#), log in, then change the password to something more memorable.
Unfortunately, I am not a good representative sample of the typical web user, and eventually I heard my clients complain about the “overly complex” autogenerated passwords. Despite the explanations of an easy workaround (copy and paste) and the added benefit of the complex passwords (more security), clients still complain.
I came up with a quick way to hack the SqlMembershipProvider to generate less complex passwords, and it was as simple as creating a new provider class that inherits from SqlMembershipProvider, then overriding the GeneratePassword method.
In the code sample below, the autogenerated password will create a fixed number of randomly generated symbols (based on the MinRequiredNonAlphanumericCharacters property), then add a series of randomly generated upper case letters to ensure a password of at least MinRequiredPasswordLength characters long (with a forced minimum of eight characters).
Using only upper case letters is just one example. You could just as easily use random digits (zero through 9), or random lower case letters. There is an argument for using random lower case letters instead of upper case letters, as people are apt to interpret an upper case “O” as a zero “0”, a problem that generally doesn’t exist with lower case letters.
Some day I may hash this out to be a more robust provider with more options on choosing the password formats, but for the immediate problem, it’s more than adequate.
namespace CustomProviders
{
public class ExtendedSqlMembershipProvider : System.Web.Security.SqlMembershipProvider
{
public ExtendedSqlMembershipProvider() : base()
{
}
public override string GeneratePassword()
{
Random rand = new Random();
StringBuilder newpassword = new StringBuilder();
int length = (this.MinRequiredPasswordLength < 8 ? 8 : this.MinRequiredPasswordLength);
int symbollength = this.MinRequiredNonAlphanumericCharacters;
int charlength = length - symbollength;
for (int index = 1; index <= symbollength; index++)
{
newpassword.Append((char)rand.Next(33, 47));
}
for (int index = 1; index <= charlength; index++)
{
newpassword.Append((char)rand.Next(65, 90));
}
return newpassword.ToString();
}
}
}
[/source]
Apparently, this doesn't work in all situations.
According to Lutz Roeder's Reflector, the CreateUserWizard control calls this:
[source='c-sharp']
if (this.AutoGeneratePassword)
{
int num1 = Math.Max(10, Membership.MinRequiredPasswordLength);
this._password = Membership.GeneratePassword(num1, Membership.MinRequiredNonAlphanumericCharacters);
}
[/source]
The Membership class (a static class) has its own password hashing algorithm that ignores the hashing of the provider.
Of course, for consistency, the PasswordRecovery control calls the provider's ResetPassword() method, which in turn calls the provider's GeneratePassword() method. This matches my testing where the custom provider outlined above will generate passwords in the alternate format when being reset (using the PasswordRecovery control), but initial passwords are in the original format (thanks to the CreateUserWizard control).
In my opinion, this is a faulty implementation -- a provider system which is ignored in one respect and recognized in another. It should not be this way.