The ARTISTS table in the cfartgallery datasource used for examples is an excellent example of how NOT to store passwords. First, they are stored in clear text and the column is limited to only 8 characters.
ARTISTID | THEPASSWORD | |
---|---|---|
4 | user@demodata.com | demo |
10 | diane@demo.com | demo |
15 | me@m.com | admin |
The next step most people take is to hash the password and store the hash in the database (assuming we increase the length of THEPASSWORD to accommodate it). Hashing provides a one way encoding of a string to a fixed length string. Below shows what the table would look like if THEPASSWORD for each user was hashed using MD5.
<cfscript>
variables.hashedPassword = Hash( form.password );
</cfscript>
ARTISTID | THEPASSWORD | |
---|---|---|
4 | user@demodata.com | FE01CE2A7FBAC8FAFAED7C982A04E229 |
10 | diane@demo.com | FE01CE2A7FBAC8FAFAED7C982A04E229 |
15 | me@m.com | 21232F297A57A5A743894A0E4A801FC3 |
What you'll notice is that the hashed password for both
user@demodata.com
and diane@demo.com
are still the same. This makes it
easier for the attacker to compromise multiple accounts because the
input password is the same for both accounts. To make the hashes unique
for each user, we need to generate a string to be appended to the
password, or a salt. The salt needs to be unique and randomly generated
for each user. Both the salt and the hashed password need to be stored
in the database. Also, change the hashing algorithm to SHA-512, because
both MD5 and SHA-1 are vulnerable to collision attacks.
<cfscript>
variables.salt = Hash( GenerateSecretKey( "AES" ), "SHA-512" );
//could use Rand("SHA1PRNG") instead of GenerateSecertKey()
variables.hashedPassword = Hash( form.password & variables.salt, "SHA-512" );
//insert both variables.salt and variables.hashedPassword into table
</cfscript>
ARTISTID | THEPASSWORD | THESALT | |
---|---|---|---|
4 | user@demodata.com | 457AEB091B4F87EB5D... | 1BF869C286A1D2A4B0... |
10 | diane@demo.com | 2F275B973C5D617F56... | 0EDCEE1F72ABD928B1 |
15 | me@m.com | ED0148FBC717B9F121... | C8FE4F050FFE75F6FA |
As you can see, THEPASSWORD for all the users is unique because they have been salted.
ColdFusion 10 enhanced Hash() by adding an additional parameter to provide the number of iterations the Hash should be run.
<cfset variables.numIterarions = 1000>
<cfquery name="request.getPwdAndSalt" datasource="cfartgallery">
SELECT THEPASSWORD, SALT
FROM ARTISTS
WHERE EMAIL = <cfqueryparam cfsqltype="cf_sql_varchar" value="#form.user#" maxlength="50">
</cfquery>
<cfif request.getPwdAndSalt.RecordCount EQ 1>
<cfif request.getPwdAndSalt.THEPASSWORD EQ Hash(form.password & request.getPwdAndSalt.SALT, "SHA-512", "utf-8", variables.numIterarions)>
<cfset SessionRotate()>
<!--- Password is good --->
<cfelse>
<!--- Bad Password --->
</cfif>
<cfelse>
<!--- Bad User --->
</cfif>
ColdFusion 11 added a function called generatePBKDFKey() which provides for a stronger one way function implementation than the hash()
function.
Here is an example of generating a password hash using generatePBKDFKey
:
<cfscript>
variables.hashedPassword = generatePBKDFKey( "PBKDF2WithHmacSHA1", form.password, variables.salt, 10000, 128 );
</cfscript>
Additional Resources: