AES-256-CBC encryption

I am trying to duplicate this PHP function in ColdFusion but I am getting an error that the Algorithm does not exist.

function encrypt_decrypt($action, $string) {
    $output = false;
    $encrypt_method = "AES-256-CBC";
    $secret_key = 'This is my secret key';
    $secret_iv = 'This is my secret iv';
    // hash
    $key = hash('sha256', $secret_key);
    // iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
    $iv = substr(hash('sha256', $secret_iv), 0, 16);
    if ( $action == 'encrypt' ) {
        $output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
        $output = base64_encode($output);
    } else if( $action == 'decrypt' ) {
        $output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
    }
    return $output;
}

I am using Lucee 5.2.3.35

My Call

<cfset var secret_key = "This is my secret key">
<cfset var secret_iv = "This is my secret iv">
<cfset var key = Lcase(Hash(secret_key, "SHA-256"))>
<cfset var iv = Lcase(Left(Hash(secret_iv, "SHA-256"),16))>
<cfset output = Encrypt(arguments.string, key, 'AES-256-CBC', 'Base64', iv)>

ERROR
Cannot find any provider supporting AES-256-CBC

I then installed Bouncy Castle and tried using this cfc

<cfcomponent displayname="Bounce Castle Encryption Component" hint="This provides bouncy castle encryption services" output="false">

<cffunction name="createRijndaelBlockCipher" access="private">
    <cfargument name="key" type="string" required="true" >
    <cfargument name="ivSalt" type="string" required="true" >
    <cfargument name="bEncrypt" type="boolean" required="false" default="1">
    <cfargument name="blocksize" type="numeric" required="false" default=256>
    <cfscript>
    // Create a block cipher for Rijndael
    var cryptEngine = createObject("java", "org.bouncycastle.crypto.engines.RijndaelEngine").init(arguments.blocksize);

    // Create a Block Cipher in CBC mode
    var blockCipher = createObject("java", "org.bouncycastle.crypto.modes.CBCBlockCipher").init(cryptEngine);

    // Create Padding - Zero Byte Padding is apparently PHP compatible.
    var zbPadding = CreateObject('java', 'org.bouncycastle.crypto.paddings.ZeroBytePadding').init();

    // Create a JCE Cipher from the Block Cipher
    var cipher = createObject("java", "org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher").init(blockCipher,zbPadding);

    // Create the key params for the cipher
    var binkey = binarydecode(arguments.key,"hex");
    var keyParams = createObject("java", "org.bouncycastle.crypto.params.KeyParameter").init(BinKey);

    var binIVSalt = Binarydecode(ivSalt,"hex");
    var ivParams = createObject("java", "org.bouncycastle.crypto.params.ParametersWithIV").init(keyParams, binIVSalt);

    cipher.init(javaCast("boolean",arguments.bEncrypt),ivParams);

    return cipher;
    </cfscript>
</cffunction>

<cffunction name="doEncrypt" access="public" returntype="string">
    <cfargument name="message" type="string" required="true">
    <cfargument name="key" type="string" required="true">
    <cfargument name="ivSalt" type="string" required="true">

    <cfscript>
    var cipher = createRijndaelBlockCipher(key=arguments.key,ivSalt=arguments.ivSalt);
    var byteMessage = arguments.message.getBytes();
    var outArray = getByteArray(cipher.getOutputSize(arrayLen(byteMessage)));
    var bufferLength = cipher.processBytes(byteMessage, 0, arrayLen(byteMessage), outArray, 0);
    var cipherText = cipher.doFinal(outArray,bufferLength);

    return toBase64(outArray);
    </cfscript>
</cffunction>


<cffunction name="doDecrypt" access="public" returntype="string">
    <cfargument name="message" type="string" required="true">
    <cfargument name="key" type="string" required="true">
    <cfargument name="ivSalt" type="string" required="true">

    <cfscript>
    var cipher = createRijndaelBlockCipher(key=arguments.key,ivSalt=arguments.ivSalt,bEncrypt=false);
    var byteMessage = toBinary(arguments.message);
    var outArray = getByteArray(cipher.getOutputSize(arrayLen(byteMessage)));
    var bufferLength = cipher.processBytes(byteMessage, 0, arrayLen(byteMessage), outArray, 0);
    var originalText = cipher.doFinal(outArray,bufferLength);

    return createObject("java", "java.lang.String").init(outArray);
    </cfscript>
</cffunction>

<cfscript>
function getByteArray(someLength)
{
    byteClass = createObject("java", "java.lang.Byte").TYPE;
    return createObject("java","java.lang.reflect.Array").newInstance(byteClass, someLength);
}
</cfscript>

</cfcomponent>

My Bouncy castle call

<cfset bc = CreateObject('component', 'model.services.bouncycastle')>
<cfset output = bc.doDecrypt(arguments.string,key,iv)>

But I get this error now.
initialisation vector must be the same length as block size

Does anyone have any suggestions on duplicating this PHP function exactly?

Thanks for any help

Well, part of the confusion here is there’s a lot of things that are non-standard in your php implementation… you’re throwing away a lot of entropy in your key and iv.

For one, PHP’s openssl_encrypt is reading
string - raw data
key - raw data (so it’s NOT decoding HEX to binary, it’s using the first 32 of 64 characters as the key)
iv - raw data (using hex characters as bytes…)

And then it’s returning base64.
Which you’re then base64 encoding again.

So you’re only using half bytes from the key hash, and only a quarter of the bytes from the IV hash.

Once I got all that worked out, the CF code is fairly simple.

First thing you need to realize is Java isn’t going to let you do anything with AES unless you install the JCE.

You’ll need the JCE which matches your JDK/JRE.
https://support.ca.com/us/knowledge-base-articles.tec1698523.html

Note this gets installed in the JRE’s system folder - it’s not a coldfusion or Lucee thing, and it’s something you’ll need to do when you update the JRE, if the JRE installs in a separate folder.

After that, encrypt Code Examples and CFML Documentation shows a CBC example at the bottom of the page.

I passed the string “this is a test” through your PHP code and got:
eG9rdGE4ZXVXbEpnb1cxOU1FRFR4Zz09

CF’s Encrypt function is expecting:
String - raw data
Key - Base64 Encoded key
Algo “AES/CBC/PKCS5Padding”
encoding “base64”
iv - string

So jumping through the steps PHP is yields:

secret_key = "This is my secret key";
secret_iv = "This is my secret iv";
string = "this is a test"
key = binaryencode(left(lcase(hash(secret_key, "SHA-256")),32).getBytes(),"base64");
iv = left(lcase(hash(secret_iv, "SHA-256")),16);
output = encrypt(string,key,"AES/CBC/PKCS5Padding","base64",iv)
output = binaryencode(output.getBytes(),"base64")

If you copy/paste the above into box repl you’ll get:
eG9rdGE4ZXVXbEpnb1cxOU1FRFR4Zz09

Which matches your source function.

On decryption you’ll have to unwrap the base64 (note i’m using output here so you can paste into the repl for testing)

input = ToString(binarydecode(output, "base64"),"UTF-8")
input = decrypt(input,key, "AES/CBC/PKCS5Padding","base64",iv)

Note the CF-java equivalent:

/* Left the same as CF Version */
secret_key = "This is my secret key";
secret_iv = "This is my secret iv";
string = "this is a test"
key = binaryencode(left(lcase(hash(secret_key, "SHA-256")),32).getBytes(),"base64");
iv = left(lcase(hash(secret_iv, "SHA-256")),16);
/* End - CF similarity */

spec = createobject("java","javax.crypto.spec.SecretKeySpec").init(BinaryDecode(key,"base64"),"AES");
ivspec = createobject("java", "javax.crypto.spec.IvParameterSpec").init(iv.getBytes());
cipher = createobject("java","javax.crypto.Cipher");

c = cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(cipher.ENCRYPT_MODE,spec,ivspec)
output2 = c.doFinal(string.getBytes());
output2 = binaryencode(output2,"base64")
output2 = binaryencode(output2.getBytes(),"base64")

I haven’t used bouncycastle before so maybe the java version can help you port to that syntax, if you can’t use the JCE for whatever reason.

2 Likes

Thanks a lot Joe, I really appreciate this.

I got it working great once I got my jar files figured out.

These are the steps I took on my windows machine.

Lucee Server changes that need to be made.
Download jce_policy-8.zip
C:\lucee\jdk\jre\lib\security
rename just so I don’t lose files.
local_policy.jar to local_policy.jar.bak
US_export_policy.jar to US_export_policy.jar.bak

extract new files from jce_policy-8.zip into the directory and restart Lucee