After to some research on securing WSE methods, I noticed there seems to be much confusion on how to hash clear password in such a way to make it work with PasswordOption.SendHashed and PasswordOption.SendPlainText when attaching a UsernameToken to the Soap request. Moreover, how to store your passwords in your database with salt that will also allow your derived UsernameTokenManager class to verify salted password correctly. There is many blogs going on with different solutions and some dead ends. The common goal seems to be to be able to store a hash of the password plus salt and have that hash be verifiable by the UsernameTokenManager.AuthenticateToken method. This is a good idea and is a popular way many Unix and other systems approach the problem.
The idea is simple. We don’t want to store plain text password in our database (i.e. SQL, text file, other) for obvious reasons. So one approach is to hash the password with something like SHA1 (I will use SHA1 as my hashing algorithm going forward) and store that. That is better then nothing, but is only slightly better then plain text. This is because it is still relatively easy to run a dictionary attack against the database if the DB is ever made public via “inside job”, accident, or poor security policies. A hacker that has a DB of already hashed passwords can just run down your user list and compare the user pw hash in the DB against all hashes in his DB. When a match is found, he has the password for that account and any other account that used the same password. A popular way to defend against this attack has been to “salt” the password with a unique string before the hash. A unique salt is generated for each user when the account is created and stored with user record (more on this latter.) The password and salt are concatenated together and then hashed to create a verifier such that:
v = SHA1(password + salt)
As we now have our “v” in the database. So our DB may look something like:
UserName Salt Verifier
staceyw 12WEe#$% asdf435rkj19-sdflkj
gatesb @#$444@#9 234-8fs;jx;k234-;jsfd
Now a client can sent us a v, instead of a clear password, and we can compare to our local copy of v to see if they match. If so, the sender must have known the salt and the password to produce the same hash value. This is still not bullet proof as a hacker can still run a dictionary attack. However it is more work and will not work with a pre-built hash-dictionary because we added the salt. That said, he can still run a dictionary attack as he will have the salt or may know how to compute the salt we used. So all he needs to do is run down the list of words in his database and calculate a new hash or “v”. This loop continues until a match is found or he runs out of dictionary words. ( See “Create our own Cracker” in future blog to see how this could be done. )
So using *unique salt for each user makes the verifier unique for each password even if two password are the same. It also makes it harder to run a standard dictionary attack. This gives us pretty good protection especially if the DB is secured and the password policy is robust.
Back to how this fits with WSE and UsernameToken. WSE allows us to attach a UsernameToken to each soap request. Among other fields, the UsernameToken contains the UserName string and the password. It also allows you to specify a enum of PasswordOption.SendHashed, PasswordOption.SendPlainText, or PassworOption.SendNone. I will ignore SendNone for this discussion. PasswordOption.SendPlainText does what is says and will send the password as entered in the constructor. Password.SendHashed will hash the password together with an Nonce and the Created DateTime of the object. The resulting hash, or digest, is what is sent in the UsernameToken in the Soap header. An important point is the that the password string returned from AuthenticateToken() *must match the password string that was passed into the UsernameToken constructor. The string need not be the clear user password, it just needs to match. Because our “v” is SHA1(password + salt); v is what we want return in AuthenticateToken(). However, that means “v” needs to be passed to the UsernameToken constructor at the client side. That means the client must know the salt before hand.
This brings us back to salt. We could ask the server for our user salt in a prior call. That is not bad as it only needs to be done once, but it is another call. There is an easier option. If we look harder, we realize that it does not matter what the salt is, only that it is unique per user and both side know what it is. Therefore, as both sides know the username, both sides could dynamically create a salt using something like .Net’s PasswordDeriveBytes class. We could define a salt generator like:
internal static string GetSalt(string userName)
if ( userName == null )
throw new ArgumentNullException("userName");
PasswordDeriveBytes pdb = new PasswordDeriveBytes(userName, null);
pdb.IterationCount = 1000;
pdb.HashName = "SHA1";
byte saltBytes = pdb.GetBytes(10);
This has a few side benefits such as we don’t need to store the salt in the DB any longer and we save a column of data. Basically, less data to store and manage. This does not decrease security as salt is not intended to be secret information anyway. In fact, it is normal (and sometimes required) for salt to be passed around in clear text. The same salt generator will be used when the account is created at the server and at the client when it creates “v”.
The method above will also work whether you choose PasswordOption.SendPlainText or PasswordOption.SendHashed. You only need to change the enum at the client and our protocol will work either way. This is because the UsernameTokenManager will handle the hashing for us automatically. Remember, we just need to return the same string that was set in the UsernameToken constructor. As we always only return “v” (or null if user not found, etc), it works either way. Naturally, we would rather use PasswordOption.SendHashed as we get the additional hashing and the security of the Nonce for free. You might use PasswordOption.SendPlainText if you wanted to encrypt the password before setting in the UsernameToken constructor at client side. Your AuthenticateToken() method would then just decrypt the password to verify (e.g. using LoginUser()) and then return the encrypted version of the password so UsernameTokenManager will see they match.
Note: Using hashes is still very vulnerable to dictionary attacks (even if using SendHashed). If you can’t count on users using very strong passwords, then I would not use hashes but crypto instead and send the cipher password using SendPlainText. See Crack your WSE SendHashed Passwords for more info.
Shortly, I will post a way to use SRP (RFC 2945) that can add even better security to your web methods/UsernameTokens.
That is about it. As this post went longer then expected, I will post a complete C# implementation in another blog. I only wish MSN Spaces allowed attaching Zip files as I could post actual projects.
William Stacey [MVP]