Saturday, August 20, 2011

Generate simplified alpha numeric passwords in SqlMembershipProvider

Pair of standard classes SqlMembershipProvider and SqlRoleProvider is often used in ASP.Net and Sharepoint applications for adding form based authentication. If you worked with FBA in Sharepoint then most probably you hear about CKS.FBA open source project which have ready for use components (web parts, templates, etc.) for adding into your Sharepoint site without additional development. Among with other components it has Password recovery web part (PasswordRecoveryWebPart class in CKS.FormsBasedAuthentication.dll assembly) which allows users who forgot their password to generate new one and get it by email. This is useful functionality, however passwords which are generated by standard components (see below) contains also punctuation marks (|./">!@#$%^&*()_-+=[{]};:<>|./?), e.g. “i=MK#{-?kwC5ol”. Often it is not convenient for end users. In this post I will show how to generate simplified passwords using only alphabetic and numeric characters. We will not also use characters which looks similar for end users and which are often reason of confusion: “i”, “1", “l”, “o”, “0”.

Internally this web part uses standard PasswordRecovery control. In order to generate new password it calls MembershipUser.ResetPassword() which in turn calls abstract method MembershipProvider.ResetPassword(). In SqlMembershiipProvider internally this method calls virtual method SqlMembershiipProvider.GeneratePassword():

   1: public virtual string GeneratePassword()
   2: {
   3:     return Membership.GeneratePassword((this.MinRequiredPasswordLength < 14) ?
   4:         14 : this.MinRequiredPasswordLength, this.MinRequiredNonAlphanumericCharacters);
   5: }

As you can see the minimal password length is 14. Also it pass MinRequiredNonAlphanumericCharacters to the static method Membership.GeneratePassword(). Msdn says about this parameter:

The minimum number of non-alphanumeric characters (such as @, #, !, %, &, and so on) in the generated password.

The keyword here is “minimum”. Often developers misunderstand it and try to pass zero in this parameter for getting only alpha numeric passwords. Lets check implementation of this method:

   1: public static string GeneratePassword(int length, int numberOfNonAlphanumericCharacters)
   2: {
   3:     string str;
   4:     int num;
   5:     if ((length < 1) || (length > 0x80))
   6:     {
   7:         throw new ArgumentException(SR.GetString("Membership_password_length_incorrect"));
   8:     }
   9:     if ((numberOfNonAlphanumericCharacters > length) || (numberOfNonAlphanumericCharacters < 0))
  10:     {
  11:         throw new ArgumentException(...);
  12:     }
  13:     do
  14:     {
  15:         byte[] data = new byte[length];
  16:         char[] chArray = new char[length];
  17:         int num2 = 0;
  18:         new RNGCryptoServiceProvider().GetBytes(data);
  19:         for (int i = 0; i < length; i++)
  20:         {
  21:             int num4 = data[i] % 0x57;
  22:             if (num4 < 10)
  23:             {
  24:                 // generate characters 0-9
  25:                 chArray[i] = (char) (0x30 + num4);
  26:             }
  27:             else if (num4 < 0x24)
  28:             {
  29:                 // generate characters A-Z
  30:                 chArray[i] = (char) ((0x41 + num4) - 10);
  31:             }
  32:             else if (num4 < 0x3e)
  33:             {
  34:                 // generate characters a-z
  35:                 chArray[i] = (char) ((0x61 + num4) - 0x24);
  36:             }
  37:             else
  38:             {
  39:                 // get a non alphanumeric character from the punctuations array
  40:                 chArray[i] = punctuations[num4 - 0x3e];
  41:                 num2++;
  42:             }
  43:         }
  44:         if (num2 < numberOfNonAlphanumericCharacters)
  45:         {
  46:             Random random = new Random();
  47:             for (int j = 0; j < (numberOfNonAlphanumericCharacters - num2); j++)
  48:             {
  49:                 int num6;
  50:                 do
  51:                 {
  52:                     num6 = random.Next(0, length);
  53:                 }
  54:                 while (!char.IsLetterOrDigit(chArray[num6]));
  55:                 chArray[num6] = punctuations[random.Next(0, punctuations.Length)];
  56:             }
  57:         }
  58:         str = new string(chArray);
  59:     }
  60:     while (CrossSiteScriptingValidation.IsDangerousString(str, out num));
  61:     return str;
  62: }

As you can see every time it inserts punctuation mark into password array it increases num2 variable (line 41). Then it checks if num2 is less then numberOfNonAlphanumericCharacters – it replaces letter or digit symbols by punctuation marks (lines 44-57). It means that whatever number you would pass in this parameter, the password anyway will contain non-alpha numeric symbols.

In order to generate simplified password we need to inherit SqlMembershipProvider and override its GeneratePassword() method (fortunately it is virtual):

   1: public class CustomMembershipProvider : SqlMembershipProvider
   2: {
   3:     private string[] characters =
   4:         new[]
   5:             {
   6:                 "a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "m", "n",
   7:                 "2", "3", "4", "5", "6", "7", "8", "9",
   8:                 "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
   9:             };
  10:  
  11:     public override string GeneratePassword()
  12:     {
  13:         string newPassword = string.Empty;
  14:         var rnd = new Random();
  15:  
  16:         int length = (this.MinRequiredPasswordLength < 14) ? 14 : this.MinRequiredPasswordLength;
  17:         for (int i = 0; i < length; i++)
  18:         {
  19:             newPassword += characters[rnd.Next(characters.Length)];
  20:         }
  21:         return newPassword;
  22:     }
  23: }

The implementation is quite simple: we defined array of allowed characters, excluding misleading characters (“i”, “1", “l”, “o”, “0”), and then use this array for password generation. As result your users will receive the following passwords: “pfwp9t42ret4jp”.

In order to use this custom membership provider you have to register it the in web.config:

   1: <membership defaultProvider="Custom">
   2:   <providers>
   3:     <add connectionStringName="..." enablePasswordRetrieval="false" enablePasswordReset="true"
   4: requiresQuestionAndAnswer="false" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed
   5:  maxInvalidPasswordAttempts="100" minRequiredPasswordLength="1" minRequiredNonalphanumericCharacters="0"
   6: passwordAttemptWindow="10" passwordStrengthRegularExpression="" name="Custom"
   7: type="CustomMembershipProvider.CustomMembershipProvider, CustomMembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..." />
   8:   </providers>
   9: </membership>

After this it will be used in your application.

1 comment: