Automatically encrypting and decrypting data with ColdFusion ORM and annotations
I’m writing an application that uses ColdFusion’s ORM features heavily. Various fields in my database deal with Personally Identifiable Information (PII) and need to be encrypted to meet regulatory requirements. I’ve been mulling over the best way to deal with keeping the data encrypted while in the database but have it be readily usable when loaded into an entity. None of the scenarios that came to mind felt right or could be implemented without an extensive amount of “work-around” code and I just wasn’t willing to go down those paths.
I put the question out to the folks that follow me on Twitter and got a couple of responses, one of which was from Mark Mandel who suggested using annotations. Now, I’ve heard the word annotation mentioned, but had never had the time/opportunity to research what they were or how they were used. That was a few weeks ago and in the meantime, I’d gotten busy focusing on other things and just got around to thinking about the encryption thing again a few days ago. What I found out astonished me.
In ColdFusion CFCs, an annotation is nothing more than an additional attribute you put on a component, property or function tag (and maybe others too, but these will do for my scenario). To test this theory, I added the attribute encrypted=”true” to the password property of my User entity (the user accounts for people that are authorized to log into and use the application) and issued an ormReload() command to recreate the ORM entities. That worked without an error, but at this point it really didn’t do anything except just add a bit of metadata to that property tag.
The secret to the automatic encryption and decryption comes when you combine the encrypted=”true” annotation with the event handling methods provided by ColdFusion ORM. ORM provides the ability to run your own custom routines at various points throughout the ORM request cycle–preInsert(), preUpdate() and postLoad() are three of the ones that I took advantage of. In essence, I used the preInsert() and preUpdate() methods to automatically replace the clear-text value of properties that had the encrypted=”true” flag with an encrypted version of that value. Similarly, I used the postLoad() method to reverse the process and replace the encrypted string that is stored in the database with the clear-text value.
I’ve included the code bits that I used below.
The password property of the User entity:
1 2 3 4 5 6
<cfproperty name="password" encrypted="true" type="string" length="100" required="true" notnull="true" />
The ORM event handler methods:
1 2 3 4 5 6 7 8 9 10 11
<cffunction name="preInsert" output="false" access="public" returntype="void"> <cfset encryptProperties() /> </cffunction> <cffunction name="preUpdate" output="false" access="public" returntype="void"> <cfset encryptProperties() /> </cffunction> <cffunction name="postLoad" output="false" access="public" returntype="void"> <cfset decryptProperties() /> </cffunction>
I then created the encryptProperties() and decryptProperties() methods to loop through the properties of the given object looking for any properties that had the encrypted=”true” attribute set and perform the associated transformation when/if one is found:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
<cffunction name="encryptProperties" output="false" access="public" returntype="void" > <cfset var props = getMetaData( this ).properties /> <cfloop array="#props#" index="prop"> <cfif structKeyExists( prop, "encrypted" ) AND prop.encrypted > <cfset variables[ prop.name ] = encrypt( variables[ prop.name ], "<key>", "AES" ) /> </cfif> </cfloop> </cffunction> <cffunction name="decryptProperties" output="false" access="public" returntype="void"> <cfset var props = getMetaData( this ).properties /> <cfloop array="#props#" index="prop"> <cfif structKeyExists( prop, "encrypted" ) AND prop.encrypted > <cfset variables[ prop.name ] = decrypt( variables[ prop.name ], "<key>", "AES" ) /> </cfif> </cfloop> </cffunction>
To make the above approach even more useful, I put all those methods in a CFC named AbstractEntity.cfc that all my ORM entity CFCs extend. So, now whenever I need to add another column to store encrypted data (no matter which entity I need them in as long as that entity extends the AbstractEntity.cfc), I define all the attributes for the property as normal and simply add the encrypted=”true” attribute to the property and ColdFusion handles the encryption and decryption for me automatically. The end result is that not only does this happen transparently to me as the developer, the clear-text values that I need to interact with in my application are encrypted when stored in the database as well as while in transit to/from the database.