Security

Secure Password Storage


Content Author

David Epler

dcepler

Reviewed/Revised By

Pete Freitag

pfreitag

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.

ARTISTIDEMAILTHEPASSWORD
4user@demodata.comdemo
10diane@demo.comdemo
15me@m.comadmin

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>
ARTISTIDEMAILTHEPASSWORD
4user@demodata.comFE01CE2A7FBAC8FAFAED7C982A04E229
10diane@demo.comFE01CE2A7FBAC8FAFAED7C982A04E229
15me@m.com21232F297A57A5A743894A0E4A801FC3

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>
ARTISTIDEMAILTHEPASSWORDTHESALT
4user@demodata.com457AEB091B4F87EB5D...1BF869C286A1D2A4B0...
10diane@demo.com2F275B973C5D617F56...0EDCEE1F72ABD928B1
15me@m.comED0148FBC717B9F121...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>

Using PBKDF instead of Hash

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: