Words from the man himself (actually, he's half the man he used to be as you can see here):
After many years of work on Web services and WSE, I’ve decided to move on. Starting next week, I’ll be working on the Windows Media Player team. I’ve appreciated working with many of you and can’t describe how great it feels to have so many cool people using this product. I wish you all the best in your future endeavors.
I have mixed feelings on this one, Keith and I were the first people on the WSE team back in the days when many people expected all we'd deliver would be some samples and Indigo was going to be shipping tomorrow anyway. A lot of water has passed under the bridge since then, including a couple of full product releases of WSE, and I'm a little sad that he's moving on. Still, let's be realistic, 2 1/2 years working with me is enough for anyone :-)
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.