As I’m (hopefully) going to release my first open source project soon, I thought it would be a good time to revisit application security, specifically password hashing, salting and general encryption. If you’ve not come across Jason Dean’s blog, I’d recommend that as a first port of call, as most of what I’m referring to is explained there.
Password security is one of those never ending journeys; you have to assume that it’ll never be perfect, and that there’s always room for improvement. I’d recently had an idea to improve password hashing a little, which someone might find useful in principle.
Let’s take a simple password, like ‘myPassword1’ (incidentally, if you actually have any passwords like that, please go and get lastpass or password1 immediately and wash your keyboard out with soap).
‘myPassword1’ stored in a database in plain text is obviously a very, very bad idea. Most people’s gut reaction would be to hash it, which at least is one way.
<cfset thePassword='myPassword1'> <cfset passwordHash=Hash(thePassword, 'SHA-512') />
But wait!, let’s assume 90% of our users are stupid, or at least, uninformed. If two people have the same password, their hashes will appear identically in the password column. On top of that, if I hash the password again, I’ll get exactly the same resultant string. So a hashed (unsalted) password is really not that useful. You could perform a ‘rainbow table’ attack: password hashes from your database can be compared against a table of known hashes (such the hash for myPassword1), et voila, password revealed.
So the next step is to introduce salting: that is, appending or prepending a unique string to the password to make the resultant hash unique, and make rainbow table attacks much much harder.
<cfset salt=createUUID() /> <cfset passwordHash=Hash(thePassword & salt, 'SHA-512') />
This has the advantage that attacks require the salt to even attempt some sort of dictionary/rainbow table attack. Usually the salt is stored alongside the hashed and salted password in the database.
This is the part which I think could be improved by a relatively small step. By storing the salt alongside the password in the users table, we’re essentially giving a hacker the necessary ammunition to attempt to compromise the password. Let’s make it a little harder. Let’s encrypt the salt itself using another key, stored outside the webroot. So I might have a folder called ‘auth’ on the same level as my ‘webroot’ or ‘html’ folder. In there, I’m going to store a text file with a UUID inside. When I want to compare a password hash, I now have to decrypt the salt before I can use it (as I acutally need the salt value, I’m not hashing it, as that’s one way) using the key read in via CFFILE outside the webroot.
<!---Get the key---> <cfset authKeyLocation=expandpath('../auth/key.txt')> <cffile action="read" file="#authKeyLocation#" variable="authkey"> <!--- New password hashing ---> <!--- Generate a salt, this is never stored in it's plain form---> <cfset theSalt=createUUID() /> <!--- Hash the password with the salt in it's plain form---> <cfset passwordHash=Hash(thePassword & theSalt, 'SHA-512') /> <!--- The encrypted salt to store in the database, using the authKey---> <cfset salt=encrypt(theSalt, authKey, 'CFMX_COMPAT')>
So why do this? If your database is compromised, you at least have an additional key to the puzzle missing for the attacker: they would have to do both a compromise of the filesystem AND the database to get anywhere. Additionally, you could quickly invalidate all the passwords in your application by deleting/regenerating the ‘master key’ at a file system level (this also assumes you have a half decent ‘forgotten/reset password’ feature in your application), which could be useful is you suspect the database and file system to be compromised at any point.
Whilst this is hardly comprehensive explanation, by adding additional layers and parts to the process, we’ll hopefully make any attackers lives a little more difficult. Jason Dean adds another step I like looping the hashing process over 1000 times – if you made this number completely arbitary between 1000 and 1500, you’d have another variable the attacker would have to get, and this one would be stored in the source code of the application itself.
So assuming you added all the above measures, you’d have to:
- compromise the database (for the password hash, and the encrypted salt),
- the source code for the application (to get the number of hashing loops), and
- get the facility to navigate outside the (potentially) locked webroot (to get the master key to decrypt the encrypted salt).