Hey Magnus,
As Andrew noted key management depends on your security requirements,
but for most of my apps that are not specifically handling PCI compliance
data I simply store the key data outside of the webroot and accessible only
to the user the application server (ACF, Lucee, etc.) runs as, and an
admin/root account. I encrypt the file using a ‘master key’ generated from
a hash of the application name, store the encrypted data in binary format
and hash the filename for further obscurity. I then load the keys in
onApplicationStart() into my security service and have a go at it. Relevant
code snippet below:
// load and initialize the SecurityService with keyring path and master key
application.securityService = new model.services.SecurityService(
keyRingPath = ‘/opt/secure/keyrings/’ & hash( this.name, ‘MD5’, ‘UTF-8’,
108 ) & ‘.bin’,
masterKey = mid( lCase( hash( this.name, ‘SHA-512’, ‘UTF-8’, 512 ) ), 32,
22 ) & ‘==’
);
// use the SecurityService to read the encryption keys from disk
application.keyRing = application.securityService.readKeyRingFromDisk();
// check if the keyring is a valid array of keys
if( !arrayLen( application.keyRing ) ) {
// it isn’t, try
try {
// to generate a new keyring file (for new application launch only)
application.keyRing = application.securityService.generateKeyRing();
// catch any errors
} catch ( any e ) {
// and dump the error
writeDump( e );
abort;
}
}
// (re)initialize the SecurityService with the keyring
application.securityService = application.securityService.init(
encryptionKey1 = application.keyRing[1].key,
encryptionAlgorithm1 = application.keyRing[1].alg,
encryptionEncoding1 = application.keyRing[1].enc,
encryptionKey2 = application.keyRing[2].key,
encryptionAlgorithm2 = application.keyRing[2].alg,
encryptionEncoding2 = application.keyRing[2].enc,
encryptionKey3 = application.keyRing[3].key,
encryptionAlgorithm3 = application.keyRing[3].alg,
encryptionEncoding3 = application.keyRing[3].enc,
hmacKey = generateSecretKey( ‘HMACSHA512’ ),
hmacAlgorithm = ‘HMACSHA512’,
hmacEncoding = ‘UTF-8’
);
// clear the keyring from the application scope
structDelete( application, ‘keyRing’ );
I then have three relevant functions within my SecurityService.cfc that
handle reading, writing and generating keys, as follows:
/**
-
@displayname readKeyRingFromDisk
-
@description I read the keyRing from disk
-
@return array
*/
public array function readKeyRingFromDisk() {
if( !fileExists( variables.keyRingPath ) ) {
return arrayNew(1);
}
var binaryJson = fileReadBinary( variables.keyRingPath );
var encJson = charsetEncode( binaryJson, “utf-8” );
var jsonArray = dataDec( encJson, ‘master’ );
return deserializeJSON( jsonArray );
}
/**
-
@displayname generateKeyRing
-
@description I generate a new random keyring and save it to disk
-
@return array
*/
public array function generateKeyRing( numeric keyLength = 128 ) {
var keyRing = arrayNew(1);
var keyStruct = {};
var algorithm = ‘’;
for( i=1; i<=3; i++ ) {
algorithm = ( ( randRange( 0, 1 ) ) ? ‘AES’ : ‘BLOWFISH’ );
keyStruct = {};
keyStruct[‘key’] = generateSecretKey( algorithm, arguments.keyLength );
keyStruct[‘alg’] = variables.algorithm & ‘/CBC/PKCS5Padding’;
keyStruct[‘enc’] = ‘HEX’;
keyRing[i] = keyStruct;
}
saveKeyRingToDisk( keyRing );
return keyRing;
}
/**
-
@displayname saveKeyRingToDisk
-
@description I save the keyRing to disk
-
@param keyRing {Array} I am the Key Ring array
-
@return void
*/
private void function saveKeyRingToDisk( required array keyRing ) {
var jsonArray = serializeJSON( arguments.keyRing );
var encJson = application.securityService.dataEnc( jsonArray, ‘master’ );
var binaryJson = charsetDecode( encJson, “utf-8” );
fileWrite( variables.keyRingPath, binaryJson );
}
My encryption (dataEnc()) and decryption (dataDec()) functions in the same
SecurityService.cfc take an optional argument (e.g. url, form, cookie, db
or master) and use the appropriately defined keys passed from the keyring
and/or initialization - in this case the ‘master’ key specified in the
Application.cfc is used and runs a multi-pass encryption/decryption on the
keyring file.
Though I don’t have these functions in the SecurityService.cfc I have
published online
at framework-one-secure-auth/SecurityService.cfc at master · ddspringle/framework-one-secure-auth · GitHub,
they are easily added and variables/logic adjusted to make each application
unique in how it handles loading, generating and writing the keyring file.
There are, of course, other ways to handle this whole process, so use your
best judgement on how to generate and secure the keys you want to use. I’ll
have to write all this up as an additional security doc on cfdocs one day
Hope it helps!
– Denny