Random Salt
Preparing for real-world eventualities
It's a constant battle!
Just when you think you understand security, someone or something reminds you of a whole aspect that you have been ignoring, usually at your peril. No matter how much you planned, prepared, worked, and worried about your plan of attack or defensive position, the job was literally never done! You had to settle for trying to be better prepared than the enemy - as opposed to being prepared for all things at all times.
Computer system security has reached this point in the minds of many in the industry. To quote one of our favorite speakers, Ted Neward, "It isn't about how wide or high the walls are anymore; you have to have the troops at the top throwing rocks and pouring hot oil down to stop the bad guys."
What can we do about it in the real world? To that end we wish to share a technique that acknowledges that we might be better off planning for the when of intrusion, rather than only the preventative aspects. The technique we're getting to is called "random salt" in some circles, and was well demoed by Microsoft's Erik Olson at Tech-Ed in Dallas this year.
While we at CriticalSites have known about this technique (and used it) for a while, noticing Erik's incorporation reminded us of how rarely it is implemented. Many people dislike the very idea because they feel it admits defeat. While we sympathize with this view, we also know that sometimes you get outmatched - and you have to have a plan to deal with that eventuality. The alternative is to surrender - and that is the real admission of defeat.
The Hypothetical Situation
What if a hacker (or an internal malcontent, for that matter) gained access to your user login information? Suppose for purposes of this conversation that you have an ASP.NET application that leverages your own authentication database via SQL Server. This is a very common scenario, and Forms Authentication is a favorite authentication method for this situation (for good reason). So you have your Login table, which contains username and password information. If a hacker could get to this data even just to read it, you would have a disaster on your hands. With this data he or she can now log in as anyone in your system and, worse, if they get write access they can create their own backdoor account, which is unlikely to be detected - and no one will be changing the password anytime soon.
On to the Technology!
Now take the aforementioned scenario and suppose you could at least prevent the database from containing the passwords in a usable form. This is the essence of the defense. While we still don't want the hacker to get into our database, our first concern is that they should not be able to log in or successfully plant a record in our user table.
The .NET namespace includes a nice neighborhood known as System.Security.Cryptography that contains all we need to make this happen. When we create an account for our hypothetical situation we will take a couple of preparation steps to make it work. First we create a random string that we will refer to as the "salt." This value is never reused from user to user and can be generated when needed. The salt is combined with the password assigned to/chosen by the user to form a new string. The more convoluted the combination, the better.
The resulting combined string is then hashed using SHA1 or MD5, and saved to the database along with the salt. This means that any number of users could have the same password and yet the salt and hash values stored in the database by definition will not be the same. The process of authentication is to take the password provided by the user (hopefully over SSL or some other secure channel), combine it with the salt, and hash it. The resulting hash is compared to the saved hash and if they match, then the password provided by this user is the same as the password used to create the account.
Let's Look at Some Code
For better illustration, in Listing 1 we have opted to not import the System.Security and other namespaces so that those are less familiar with it will easily see the lineage of all the objects.
Let's look at that code a little at a time and put it in context. If you are familiar with Forms Authentication, then you already have most of the picture. You set up your Web.Config for Forms Authentication and build your login form (i.e., login.aspx). Normally you would put some database lookup logic in the submit button's click event to retrieve the username and password to compare with what the user provided.
If they match, then you are in clover - the cookie gets issued and, if you did it correctly, the user is redirected to the original requested page. The twist here is the password-compare thing. We don't want to store the password in the database. We still need to use SSL for the login.aspx page since the password is being passed (just not stored). Okay, given that lead-in, let's examine some code.
We start by creating our byte arrays to do our comparisons. We need one for the password sent by the user:
' Encoding is found in
' System.Text.Encoding
Dim PWDArray() As Byte = Encod
ing.Unicode.GetBytes(strPassword)
And one each for the salt and hash values we pull out of the database. These are stored in the same record as the username:
' Convert is from System.Convert
Dim SaltArray() As Byte = Convert.FromBase64String(strSalt)
Dim HashArray() As Byte = Convert.FromBase64String(strHash)
SHA1 is a useful hash and my choice for this demo. The SHA1 object has a create method that returns a HashAlgorithm, which we will use directly as a parameter to the CryptoStream constructor:
' SHA1 from System.Security
' .Cryptography.SHA1
Dim HashResult As HashAlgorithm = SHA1.Create()
' CryptoStream from
' System.Security
' .Cryptography.CryptoStream
Dim csRecreateHash As CryptoStream
We use the CryptoStream object, csRecreateHash, to combine the password with the salt. This method must match the steps used to create the hash stored in the database when the account was created (or the last time the user password was changed):
' Hash the password we got back
' with the salt we stored
csRecreateHash = New CryptoStream(Stream.Null, HashResult,
CryptoStreamMode.Write)
csRecreateHash.Write(PWDArray, 0, PWDArray.Length)
csRecreateHash.Write(SaltArray, 0, SaltArray.Length)
csRecreateHash.FlushFinalBlock()
csRecreateHash.Close()
You might remember the HashResult from above. It was also one of those parameters from the CryptoStream constructor:
Dim HashRecalculated() As Byte = HashResult.Hash
Now we're back in the real world. Compare the hash from the database (HashArray) to the one we just recalculated (HashRecalculated). If they match, then the user is legit and should be given the nod:
' Compare the recalculated hash
' with the one stored in the
' database
bValidUser = CompareAr
rays(HashRecalculated, HashArray)
The rest you can wade through on your own; let the comments be your guide. What used to be a single line becomes a big lump, but a reusable lump. Take this technique and use it; we need all the help we can get to hold back the barbarians at the gates.
Conclusion
Even with this code, you still have a long way to go to be secure. But as you build your bag of tricks, it is hard to truly be fully secure without a little random salt in your application.
Resources
MSDN Best Practices Page: http://msdn.microsoft.com/security/securecode/
bestpractices/default.aspx
Microsoft Security Developer Center: http://msdn.microsoft.com/security
Patterns and Practices: www.microsoft.com/resources/practices
Author Bios
Patrick Hynds, MCSD, MCSE+I, MCDBA, MCSA, MCP+Site Builder, MCT, is the CTO for CriticalSites. Named by Microsoft as the Regional Director for Boston, he has been recognized as a leader in the technology field. An expert on Microsoft technology and
experienced with other technologies as well, Patrick previously taught freelance
software development and network architecture. In spite of the demands of his management role at CriticalSites, Patrick stays technical and in the trenches, acting as project manager and/or developer/engineer on selected projects throughout the year.
phynds@criticalsites.com
Bruce Backa, CEO of CriticalSites, is a noted business leader and consultant in the IT industry. He has acted as chief architect, technologist, and project manager for assignments involving large-scale technology and implementation strategies. He has held the positions of director of Technology and Business Research for the American Stock Exchange (AMEX) and director of Technology for American International Group.
bbacka@criticalsites.com
Listing 1
' Encoding is found in System.Text.Encoding
Dim PWDArray() As Byte = Encoding.Unicode.GetBytes(strPassword)
' Convert is from System.Convert
Dim SaltArray() As Byte = Convert.FromBase64String(strSalt)
Dim HashArray() As Byte = Convert.FromBase64String(strHash)
' SHA1 from System.Security.Cryptography.SHA1
Dim HashResult As HashAlgorithm = SHA1.Create()
' CryptoStream from System.Security.Cryp
' tography.CryptoStream
Dim csRecreateHash As CryptoStream
' Hash the password we got back with the salt we
' stored
csRecreateHash = New CryptoStream(Stream.Null, HashResult,
CryptoStreamMode.Write)
csRecreateHash.Write(PWDArray, 0, PWDArray.Length)
csRecreateHash.Write(SaltArray, 0, SaltArray.Length)
csRecreateHash.FlushFinalBlock()
csRecreateHash.Close()
Dim HashRecalculated() As Byte = HashResult.Hash
'Compare the recalculated hash with the one stored in
' the database
bValidUser = CompareArrays(HashRecalculated, HashArray)
' The following test determines the course of action
' you take from here
If bValidUser Then
' Here you do what you will with your
' authenticated ' user
Else
'Kick this user to the curb, they aren’t valid
End If
Function CompareArrays(ByVal FirstArray As Byte(), ByVal SecondArray As Byte()) As
Boolean
Dim result As Boolean = False
Dim bNotEqual As Boolean = False
Dim i As Int32
If FirstArray.Length = SecondArray.Length Then
For i = 0 To FirstArray.Length - 1
If FirstArray(i) <> SecondArray(i) Then
bNotEqual = True
Exit For
End If
Next I
End If
If bNotEqual Then
CompareArrays = False
Else
CompareArrays = True
End If
End Function
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com