WSE 2.0 Authentication: How To Avoid ClearText Passwords With UsernameToken

0 comments suggest edit

Aaron Skonnard mentionsthat

When you take the custom authentication route and write a UsernameTokenManager (UTM), your implementation of AuthenticateToken must return the same secret (e.g., password) used on the client side to generate the hash/signature, depending on which option you use.

As he correctly points out, this makes security experts cringe and hide under the bed (see Keith Brown’s cringing response where he proposes a solution).

The big issue is that your UsernameTokenManager needs access to the original cleartext password. But like any good security conscious developer, you don’t store passwords as cleartext, do you? (I sure hope not. Bad security conscious developer. Bad!). Hopefully you do something along the likes of what Keith suggests in his MSDN column. For each user, he stores a randomly generated salt value and a hash of the cleartext password combined with salt value. The salt value is unique per user.

Keith points out that the secret returned by the AuthenticateToken method doesn’t have to be the actual cleartext password. It just has to match the secret sent by the client. So if you store your passwords as an SHA1 hash, your client just needs to hash the password before creating the UsernameToken.

However, if you store your password as an SHA1 hash of the cleartext password + salt value, you’re going to have to do a little more work. Your client isn’t going to know the salt value for every user, so your client needs a way to discover that. This may require calling a separate web method just to query for the salt value given a user name. Service clients would be required to store that value (probably on a “session” basis) and use it when calling methods on the main web service.

Below is some sample code for doing just that. This assumes that user passwords are stored as described in the aforementioned article using salt and hash (no eggs, but do bring the ketchup). (My apologies for the ugly formatting, I didn’t want the code to be too wide)

//Make an initial web service call to get the 
//the salt value for the user "haacked".  
//This should be stored by the client so its 
//not called for every method of our main service.
MyServiceWse proxy = new MyServiceWse();

//In order to get the salt value, a special account
//"saltAdmin" is used to call GetSalt().  This account
//only has access to this method.
//This also requires that the client app knows the;
//saltAdmin's salt value up front.
string adminPassword = GetAdminPassword(); 
//implementation not shown.

UsernameToken adminToken 
    = new UsernameToken("saltAdmin", adminPassword
                    , PasswordOption.SendHashed);

proxy.RequestSoapContext.Security.Tokens.Add(adminToken);
string username = "haacked";
string salt = proxy.GetSalt(username);
proxy.RequestSoapContext.Clear();

// Hash password and salt.
string pw = "Password"; //assume this came from the user.
SHA1CryptoServiceProvider hashProvider 
    = new SHA1CryptoServiceProvider();

byte[] inputBuffer = Encoding.Unicode.GetBytes(pw + salt);
byte[] result = hashProvider.ComputeHash(inputBuffer);
string hashedPassword = Convert.ToBase64String(result);
//Set up the user's token.
//Notice we the hashed password instead of the cleartext one.
UsernameToken token 
    = new UsernameToken(username, hashedPassword
                    , PasswordOption.SendHashed);

proxy.RequestSoapContext.Security.Tokens.Add(token);

//Make the actual service call.
proxy.SomeWebServiceMethod();

The AuthenitcateToken method of your custom UsernameTokenManager class can now just return the hashed password value for the calling user from your data store and everything will work just fine and security experts can come out from under the bed.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

6 responses

  1. Avatar for Jagdeep
    Jagdeep September 15th, 2004

    The downside to this approach is that the salt is sent across the wire in clear text. The slat is the missing link in the password puzzle, if this is guessed then games up. Does your approach need to compare the password with a value stored in the database? How will you compare the hash to a hash in the database? I may have missed something.



    Regards

    Jagdeep

  2. Avatar for Haacked
    Haacked September 15th, 2004

    Well the salt isn't enough to guess the password. They still have to break MD5 or SHA1. (whoops. Guess they did recently. ;)



    The value in the database is the hashed password. So I compare the hashed value sent over the wire, with the hashed value in the database. But since I'm using PasswordOption.SendHashed, what is actually getting sent over the wire is the hash of the hash.



    So the secret is never sent over the while. The main focus of my approach is not to protect the secret. As long as you use PasswordOption.SendHashed, the secret is safe.



    The point is to not store plaintext passwords. If you register on my site and someone compromises my database, they'll be able to login as you to my site. But not to any other as they don't have your original plaintext password. That is NEVER sent in any form.

  3. Avatar for Jon
    Jon November 25th, 2004

    Hi, I am up against this same problem with the password hashed in a database. No offence because I cannot offer a better solution than your's but here is the problems I see with it.



    The beauty of WSE is the ability to say to fellow developer here is the service authenticate by WSE using your credentials. You should not have to explain how to Hash the password before sending and using web services to grab the salt etc.



    But most importantly the password is hashed in the database to make it useless to a potential hacker who actually happens to find it. With the above solution if a hacker is able to grab the password from your database he is able to authenticate to your service directly, without the hashing and salt requesting above (it is already hashed for him)



    So in effect it is easier for a hacker with a hash of the password stolen from your DB in his possesion to authenticate to your service than it is a legitimate developer with the original password.



    From what I can see the only soltions appear to be to have the password unhashed in the DB and protect it with SQL encryption and SQL security, or only accept plain text passwords from WSE.

  4. Avatar for William
    William January 12th, 2005

    I blogged a solution that using salt where you don't need a seperate call to get the salt. You can gen a salt based on username and both sides know that before hand so both side can gen same salt.



    http://spaces.msn.com/members/staceyw

    --wjs

  5. Avatar for Sean
    Sean May 8th, 2005

    SALT needs to be random. And never repeated. If you happen to store to passwords, or send two encrypted texts using the same password/salt combination, you just gave an attacker a big head-start. If the salt can be calculated each time, then it can't be random.



    Sean

  6. Avatar for Mp3 Shocks
    Mp3 Shocks June 20th, 2007

    Well the salt isn't enough to guess the password. They still have to break MD5 or SHA1. (whoops. Guess they did recently. ;)