views:

678

answers:

4

I'm using a website as a frontend and all users are authenticated with the standard ASP.NET Membership-Provider. Passwords are saved "hashed" within a SQL-Database.

Now I want to write a desktop-client with administrative functions. Among other things there should be a method to reset a users password. I can access the database with the saved membership-data, but how can I manually create the password-salt and -hash? Using the System.Web.Membership Namespace seems to be inappropriate so I need to know how to create the salt and hash of the new password manually.

Experts step up! :)

+1  A: 

Quick dirty method

Public Shared Function GetSaltKey() As String
            Dim saltBytes() As Byte
            Dim minSaltSize As Integer = 4
            Dim maxSaltSize As Integer = 8

            ' Generate a random number for the size of the salt.
            Dim random As Random
            random = New Random()

            Dim saltSize As Integer
            saltSize = random.Next(minSaltSize, maxSaltSize)

            ' Allocate a byte array, which will hold the salt.
            saltBytes = New Byte(saltSize - 1) {}

            ' Initialize a random number generator.
            Dim rng As RNGCryptoServiceProvider
            rng = New RNGCryptoServiceProvider()

            ' Fill the salt with cryptographically strong byte values.
            rng.GetNonZeroBytes(saltBytes)

            ' Convert plain text into a byte array.
            Return Convert.ToBase64String(saltBytes)
        End Function

        Public Shared Function ComputeHash(ByVal password As String, ByVal salt As String) As String

            Return System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(salt & password, _
                System.Web.Configuration.FormsAuthPasswordFormat.SHA1.ToString)
        End Function

Although, the membership namespace has stuff built in for this as well, as stated by Forgotten Semicolon

Charlie Brown
Nice method but not the same as the standard asp.net membership-provider is using - after setting a new password with this method the user couldn't log-in anymore. Thanks anyway :)
Anheledir
I am using this code with the asp.net membership provider on a production site (an old one), you recompute the hash and salt key and compare on each authentication, which is where the security is derived from. Not to be picky, but your question only asked how to compute the salt and has key manually.
Charlie Brown
+1  A: 

It's been some time since I've tinkered with ASP.Net membership, but do remember dealing with something a bit related to it (needed to customize things due to an existing database of users). In that effort I overrode the methods (the existing user db had md5 hashed pwds).

So in the same "line of thought":

Expose the Membership API via a web service that your desktop app can reference. This way, you're not "re-creating" things, you're re-using them. You don't have to override anything, you're just exposing the existing methods via a web service for your desktop app.

Goes without saying that you'd have to secure this endpoint....

If the above is too sketchy for your taste, here's a link to the asp.net forums regarding some attempts to recreate the hashing....I can't confirm the accuracy, but it should be easy to test it out:

http://forums.asp.net/p/1336657/2899172.aspx

EdSF
Thank you for your web-service idea. You're right, under normal conditions I would make the whole Membership-Thing available via a service. Unfortunately company-policies aren't allowing this kind of web-service :(
Anheledir
+1  A: 

I used reflector to take a look at those methods the .NET-Framework is using internal. Maybe there are public methods available for this but I did not find them - if you know how to query those internal methods as a user please left a comment! :)

Here is the simplified source-code without unnecessary conditions because I only want to encode the password as a SHA1-Hash:

private string GenerateSalt() {
  var buf = new byte[16];
  (new RNGCryptoServiceProvider()).GetBytes(buf);
  return Convert.ToBase64String(buf);
}

private string EncodePassword(string pass, string salt) {
    byte[] bytes = Encoding.Unicode.GetBytes(pass);
    byte[] src = Convert.FromBase64String(salt);
    byte[] dst = new byte[src.Length + bytes.Length];
    byte[] inArray = null;
    Buffer.BlockCopy(src, 0, dst, 0, src.Length);
    Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
    HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
    inArray = algorithm.ComputeHash(dst);
    return Convert.ToBase64String(inArray);
}
Anheledir
no need to use Reflector, the source for the default providers is readily available - http://download.microsoft.com/download/a/b/3/ab3c284b-dc9a-473d-b7e3-33bacfcc8e98/ProviderToolkitSamples.msi . Take a look at the EncodePassword method of SqlMembershipProvider for implementation. It uses the default hash algorithm unless one is specified for the MembershipProvider. IIRC, the default is SHA-1
Russ Cam
+4  A: 

You can absolutely use System.Web.Security within a console or winforms app.

Here's a simple console application:

static void Main(string[] args)
{
    MembershipProvider provider = Membership.Provider;

    MembershipUser myUser = provider.GetUser("myUser", false);

    if( myUser != null ) provider.DeleteUser("myUser", true);

    MembershipCreateStatus status;

    myUser = provider.CreateUser("myUser", "password", "[email protected]", null, null, true, null, out status);

    if (status != MembershipCreateStatus.Success)
    {
        Console.WriteLine("Could not create user.  Reason: " + status.ToString());
        Console.ReadLine();
        return;
    }

    Console.WriteLine("Authenticating with \"password\": " + provider.ValidateUser("myUser", "password").ToString());

    string newPassword = myUser.ResetPassword();

    Console.WriteLine("Authenticating with \"password\": " + provider.ValidateUser("myUser", "password").ToString());
    Console.WriteLine("Authenticating with new password: " + provider.ValidateUser("myUser", newPassword).ToString());

    Console.ReadLine();
}

And the app.config:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
        <add name="MyConnectionString" connectionString="Data Source=localhost;Initial Catalog=MyDatabase;Integrated Security=True" providerName="System.Data.SqlClient" />
    </connectionStrings>
    <system.web>
        <membership defaultProvider="MyMembershipProvider">
            <providers>
                <clear />
                <add name="MyMembershipProvider"
                     type="System.Web.Security.SqlMembershipProvider"
                     connectionStringName="MyConnectionString"
                     applicationName="MyApplication"
                     minRequiredPasswordLength="5"
                     minRequiredNonalphanumericCharacters="0"
                     requiresQuestionAndAnswer="false" />
            </providers>
        </membership>
    </system.web>
</configuration>
Forgotten Semicolon