September 10, 2004

UsernameToken - SendHashed

Scott Watermasysk posts about UsernameToken hashed passwords and Julia Lerman adds comments in which she says "the database needs to store clear text" although the recommended method is to "store a user's password as a hash, or even better a salted hash" in the database. I've thought about these posts a bit, here are my comments, hopefully they'll be useful for people trying to use these schemes.

First, some basics. The word password, when used with the UsernameToken means password or password equivalent. There is absolutely no requirement that the value you use to construct the UsernameToken at the client is exactly what the user typed in. The only absolute requirement is that the value used in the constructor at the client matches the value returned by your UsernameToken.AuthenticateToken method. Thus the simplest case where you want to use plaintext passwords in the database and directly compare the value in the token to that in the database.

Now, the UsernameToken has three options for password management associated with it, each of which are defined in the OASIS WSS UsernameToken Profile:

1) SendNone. This is obvious, the password is never sent on the wire with the token. Not a lot of use unless you're just authenticating with usernames, clearly that's not terribly secure.

2) SendPlainText. Again, this is hopefully obvious and maps to the wsse:PasswordText option in the token profile. When you use this option the plaintext form of the password supplied on the UsernameToken constructor is included in the token on the wire. Clearly, using this option presents some security issues: either the token itself must be encrypted for the target service or the whole message exchange must occur on a secure channel, for example SSL.

3) SendHashed. Not so obvious. The one thing it isn't is a simple hash of the password provided on the constructor. Instead it maps to the wsse:PasswordDigest option in the token profile. This hashes 3 values from the UsernameToken instance together: created time, nonce and password. This process binds the password value to other elements in the UsernameToken instance: every time the UsernameToken is used to send a message, the created time and nonce are updated and the hash value is recomputed. Maybe we'll rename this option to "SendDigest" in the next release.

Now back to Scott and Julias posts, specifically storing either password hashes or salted hashes in the database. These are indeed good models for storing passwords at the server since the password values themselves are not actually retained. However, neither a plain hash of a password or a salted hash of a password in these models is the same as the SendHashed option of the UsernameToken.

Let's consider the two server password models separately and see what will work if you want to use SendHashed.

First, the server stores a simple hash of the password, for example SHA-1. The client knows (a) the plain password (b) the nonce (c) the created time. In contrast the server knows (a) the hashed password (b) the nonce (c) the created time. Using SendHashed alone just won't work since it's not the same hash as the server uses and the UsernameToken.AuthenticateToken method cannot return the same password value that the client used on the constructor. However, if the client hashes the password before constructing the UsernameToken then both sides know the same thing - the hashed password equivalent.

Second, the salted hash model. The client knows (a) the plain password (b) the nonce (c) the created time. In contrast the server knows (a) the hashed password (b) the nonce (c) the created time and (d) the salt. Note that the client doesn't know the salt and thus hashing the password before passing it to the UsernameToken constructor won't work. There's no way in this model for the client and server to have shared knowledge of the same password or password equivalent so SendHashed won't work. The only option with salted hashes is to use SendPlainText as follows: at the server, your modified UsernameToken.AuthenticateToken method takes the supplied password and hashes it with the salt. It then compares the result with the value in the database, if the two match, it returns the password provided in the UsernameToken allowing authentication to succeed.

I hope this sheds a little light on this topic, I'm happy to hear your comments and suggestions if there's something I've missed or something you'd like to see changed in the next version of WSE.

Posted by herveyw at September 10, 2004 12:39 AM
Comments

I wrote up an approach that I think is sound for approaching this.

http://haacked.com/archive/2004/09/09/1177.aspx

What do you think?

Posted by: Haacked at September 10, 2004 05:21 PM

if you do the first thing you've suggested (using the hashed password as the input to the token digest) aren't you are effectively converting the stored hash into a secret? Anyone who knows a user's hash (not the password) can now authenticate as the user. More importantly, doesn't this undermine the purpose of storing the hash and not the password -- the point was to avoid storing secrets and the hashes are all now secrets that must be protected.

Posted by: pete dapkus at September 10, 2004 10:46 PM

There are technologies that allow you to store a salted password hash on the server, and still be able to authenticate without sending clear-text passwords (or equivalent) on the wire. I'm thinking of Zero Knowledge Password Proof (ZKPP) technologies like SRP, ARP, SPEKE, etc.

These technologies aren't event terribly difficult to implement. I did an implementation of SRP-6 myself, and it was a surprisingly small amount of code (except for the lack of an arbitrary precision integer class in the .NET Fx).

One problem with these technologies is that they always require more than one message to authenticate (as for challenge-response), which may make them unsuitable for some kinds of web service applications.

A more troubling issue is that this family of technologies is encumbered by patents. Unless some arrangement can be made to allow these ZKPP technologies to be used freely, we will be forced to keep finding clever ways to hide password encryption keys.

Posted by: Andy Neilson at September 12, 2004 06:15 AM

That should be "AMP" not "ARP".

Posted by: Andy Neilson at September 12, 2004 07:33 AM

Pete: No, because I'm still not sending the hash as cleartext. I'm sending it using PasswordOption.SendHashed, thus double hashing it.

Yes, the hashed password becomes the "secret", but the point is that this is better than having the password stored as cleartext in the database. In that case, if I get access to your password there, that might get me access to other sites you do business with. But if the password is hashed in the db, then that password is useless elsewhere.

Posted by: Haacked at September 13, 2004 12:16 PM