For extra security, you may want to encrypt important information in your database so that someone who hacks in can't read it easily. For instance, you could be saving credit card information so that you can bill for services, or you could be saving the customer's sensitive business information. Here's the method I use to encrypt and decrypt important information.
<cfcomponent>
<cfset this.key = 'SomeRandomLongString1234567890' />
<cffunction name="createAccount">
<cfargument name="fullName" required="yes" />
<cfargument name="companyName" required="yes" />
<cfargument name="email" required="yes" />
<cfargument name="ccNum" required="yes" />
<cfquery datasource="someDSN">
INSERT INTO accounts (fullName, companyName, email, ccNum)
VALUES (
<cfqueryparam type="cf_sql_varchar" value="#Encrypt(ARGUMENTS.fullName, THIS.key)#" />
<cfqueryparam type="cf_sql_varchar" value="#Encrypt(ARGUMENTS.companyName, THIS.key)#" />
<cfqueryparam type="cf_sql_varchar" value="#Encrypt(ARGUMENTS.email, THIS.key)#" />
<cfqueryparam type="cf_sql_varchar" value="#Encrypt(ARGUMENTS.ccNum, THIS.key)#" />
)
</cfquery>
<cfreturn />
</cffunction>
</cfcomponent>
So instead of storing clear text in the database, such as a credit card number of '4123 1234 1234 1234', you'll be saving an encrypted string like 'dY23dsRT$*h_^dfg34d'. If anyone manages to compromise your database, you've just made it fairly more difficult for them to actually get at the original information you've saved.
So how do you easily get access to your decrypted information? You could decrypt your strings in application code as you need them, but better yet you add a function to automatically decrypt the data you get back out of the database:
<cffunction name="decryptQuery">
<cfargument name="query" required="yes">
<!--- For each of the rows of the query --->
<cfoutput query="ARGUMENTS.query">
<!--- For each of the columns --->
<cfloop index="column" list="#ARGUMENTS.query.columnList#">
<!--- Attempt to decrypt the column --->
<cftry>
<cfset QuerySetCell(ARGUMENTS.query, column, Decrypt(ARGUMENTS.query[column][currentRow], THIS.ekey), currentRow) />
<cfcatch type="any"></cfcatch><!--- If the information isn't encrypted, it will be left as is --->
</cftry>
<cfloop>
</cfoutput>
<cfreturn />
</cffunction>
Then you call the DecryptQuery function each time you query encrypted information from the database:
<cffunction name="getAccount" returntype="query">
<cfargument name="accountID" required="yes">
<cfset var qAccount = '' />
<!--- Get the encrypted account information from the database --->
<cfquery name="qAccount" datasource="someDSN">
SELECT fullName, companyName, email, ccNum
FROM accounts
WHERE accountID =
<!--- Decrypt any encrypted information in the query --->
<cfset DecryptQuery(qAccount) />
<cfreturn qAccount />
</cffunction>
The final component would look like so:
<cfcomponent>
<cfset this.key = 'SomeRandomLongString1234567890' />
<cffunction name="createAccount">
<cfargument name="fullName" required="yes" />
<cfargument name="companyName" required="yes" />
<cfargument name="email" required="yes" />
<cfargument name="ccNum" required="yes" />
<cfquery datasource="someDSN">
INSERT INTO accounts (fullName, companyName, email, ccNum)
VALUES (
<cfqueryparam type="cf_sql_varchar" value="#Encrypt(ARGUMENTS.fullName, THIS.key)#" />
<cfqueryparam type="cf_sql_varchar" value="#Encrypt(ARGUMENTS.companyName, THIS.key)#" />
<cfqueryparam type="cf_sql_varchar" value="#Encrypt(ARGUMENTS.email, THIS.key)#" />
<cfqueryparam type="cf_sql_varchar" value="#Encrypt(ARGUMENTS.ccNum, THIS.key)#" />
)
</cfquery>
<cfreturn />
</cffunction>
<cffunction name="getAccount" returntype="query">
<cfargument name="accountID" required="yes">
<cfset var qAccount = '' />
<!--- Get the encrypted account information from the database --->
<cfquery name="qAccount" datasource="someDSN">
SELECT fullName, companyName, email, ccNum
FROM accounts
WHERE accountID =
<!--- Decrypt any encrypted information in the query --->
<cfset DecryptQuery(qAccount) />
<cfreturn qAccount />
</cffunction>
<cffunction name="decryptQuery">
<cfargument name="query" required="yes">
<!--- For each of the rows of the query --->
<cfoutput query="ARGUMENTS.query">
<!--- For each of the columns --->
<cfloop index="column" list="#ARGUMENTS.query.columnList#">
<!--- Attempt to decrypt the column --->
<cftry>
<cfset QuerySetCell(ARGUMENTS.query, column, Decrypt(ARGUMENTS.query[column][currentRow], THIS.ekey), currentRow) />
<cfcatch type="any"></cfcatch><!--- If the information isn't encrypted, it will be left as is --->
</cftry>
<cfloop>
</cfoutput>
<cfreturn />
</cffunction>
</cfcomponent>
Now, as I said, this won't stop hackers from getting at your sensitive information, but it will make it much harder for them to exploit it. Now all of your database backups will be safer if they fall into the wrong hands. For even better security, though, you could specify a stronger encryption algorithm and a salt. See the docs for the Encrypt() function for more information.
So... anyone have any ideas on how to keep the encryption key out of the CFML code but still available at runtime? You can't store it in the database or on the filesystem. I suppose you could store it in server memory, but then you'd need to re-enter it every time the server or application was re-started.

Comments (4)
February 3, 2009
9:07PM | #
The chances of getting your file system AND database hacked/stolen at the same time are much slimmer than getting just one of the two hacked. So I think keeping the key in a cfml file is as safe as anyone could expect you to be.
Unless you delete a file called key.txt after your application starts and then upload the file when you need to restart the application (key gets copied to an app var). At least then the key is never kept at the same physical location or on the same network. I think it's an overkill though.
February 3, 2009
9:47PM | #
Keeping the key in a file outside of web root may protect from certain possibilities.
While different from the use case you describe it's also worth nothing the use of hash(). In the db the value is stored hashed, and you compare the hashed values to see if they are equal. Great for doing password comparisons, but not useful if you ever needed that CC number again since the hash is one way.
hash("#mykey##password##mysalt#", "SHA-512") or something along those lines where the salt is unique per record
Regarding storage of the key, perhaps just outside the webroot would be a bit better then not for some security scenarios. Though if they have access to your file system ultimately they should be able to use anything on the server to compromise.
February 6, 2009
9:41AM | #
Two standards at my company is to have a simple cfc for encrypting/decrypting data and it holds the key as well. Then the cfc itself is encrypted using cfencode. The other thing is we don't decrypt data until we actually need it. That way if there ever was a security hole somewhere that allowed arbitrary code to run dumping variables wouldn't yield sensitive data.
February 6, 2009
10:04AM | #
Robert, kudos to your company-- encapsulating your encryption and decryption functions in a cfencoded, standalone CFC is a very smart technique for further obscuring the encryption key. (For those of you who don't know, Robert is referring to a binary program called
cfencodethat you can use to encrypt source code files, leaving the file executable while making it impossible to read the original source [unless you have access to the not-so-common decoder program]).But can you clarify what you mean when you write that you "don't decrypt data until we actually need it"? I assume this means that you decrypt the data on the fly for calculations or display, but never store the decrypted data in a variable.