Thursday, December 3, 2009

ColdFusion: Encryption Interoperability Issues (Beginner)

Recently, I have seen a few questions about encryption interoperability. The libraries used by ColdFusion (Sun JCE) are pretty standard. So for the most part, compatibility should not be issue. But it got me to thinking about some of the common pitfalls when trading encrypted data with external tools. While they may seem obvious to some of you, many of them were not at all obvious to me.


While I am certainly no expert on the subject, most of the issues I have encountered, or seen in various forums, tend to involve two things: cipher settings and encoding. Now I know that seems like a blatantly obvious statement. But it is the source of more problems than you might think.

Unfortunately, the ColdFusion documentation on the encrypt/decrypt functions is a bit sketchy in places. Now to a degree, the minimal documentation is understandable. Realistically it would require whole volumes to provide a comprehensive explanation of encryption. But there are a few key aspects of the encrypt/decrypt functions that I feel could really use some illumination. If only to help developers avoid some of the more common interoperability problems.

A prime example is the algorithm argument. Most examples you will see use simple names like AES or DESEDE. What is not immediately obvious is that those simple names are short-hand for several settings: the algorithm, cipher mode and padding scheme. When you use the short-hand name, ColdFusion applies the default cipher mode and padding scheme automatically. The defaults may vary depending on which algorithm you select. But in the case of AES the defaults are ECB and PKCS5Padding. (At least from what I can tell). So in other words, the algorithm values AES and AES/ECB/PKCS5Padding are equivalent. To specify a different mode or padding just change the algorithm value.

Now chances are the external tool you are working with probably does not use exactly the same defaults as ColdFusion. But once you are aware of the additional settings for the various algorithms, it is much easier to figure out how to align the results. You just need to ensure the settings on both ends match up.

Take C# for example. The defaults for the RijndaelManaged class are a bit different than ColdFusion's. For example, the default mode is CBC rather than ECB and the default key size is 256 bit. So exchanging values between the two may not work right off the bat.

                    ColdFusion            C# (RijndaelManaged)
Mode: ECB CBC
Padding: PKCS5Padding PKCS7
Key Size: 128 bit 256 bit
Block Size: 128 bit 128 bit
Allowed Key Lengths 128, *192, *256 bit 128, 192, 256 bit

* Note: Unlimited encryption is required in CF for keys larger than 128 bit

Once you have aligned the cipher settings, double check the encoding used on the various values. In ColdFusion the string to be encrypted/decrypted "is always interpreted as a UTF-8 string". From what I observed, the key value seems to be interpreted as base64. So using this collective information you can now easily adjust the settings of ColdFusion and C# to produce the same results.
<!---
Encrypt/Decrypt
---->
<h3>ColdFusion (AES/CBC/PKCS5Padding) + iv</h3>

<cfset thePlainData = "Nothing to see here folks" />

<cfset theKey = generateSecretKey("AES", 128) />
<cfset theAlgorithm = "AES/CBC/PKCS5Padding" />
<cfset theEncoding = "base64" />
<cfset theIV = BinaryDecode("7fe8585328e9ac7b7fe8585328e9ac7b", "hex") />

<cfset encryptedString = encrypt(thePlainData, theKey, theAlgorithm, theEncoding, theIV) />
<cfset decryptedString = decrypt(encryptedString, theKey, theAlgorithm, theEncoding, theIV) />


<!---
Display results
--->
<cfset keyLengthInBits = arrayLen(BinaryDecode(theKey, "base64")) * 8 />
<cfset ivLengthInBits = arrayLen(theIV) * 8 />
<cfdump var="#variables#" label="AES/CBC/PKCS5Padding Results" />



using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;

namespace AESTest
{
public class AESCBC
{
public static void Main()
{
try
{
// Just hard coded values for testing ...
// MUST change them to match the values used in the CF code
String thePlainData = "Nothing to see here folks";
String theKey = "oRJUjgbx9SGGR6v3T8JGJg==";
String theIV = "f+hYUyjprHt/6FhTKOmsew==";
String encryptedText = EncryptText(thePlainData, theKey, theIV);
String decryptedText = DecryptText(encryptedText, theKey, theIV);

Console.WriteLine("Encrypted String: {0}", encryptedText);
Console.WriteLine("Decrypted String: {0}", decryptedText);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}

Console.ReadLine();
}


public static String EncryptText(String Data, String Key, String IV)
{
// Extract the bytes of each of the values
byte[] input = Encoding.UTF8.GetBytes(Data);
byte[] key = Convert.FromBase64String(Key);
byte[] iv = Convert.FromBase64String(IV);


// Create a new instance of the algorithm with the desired settings
RijndaelManaged algorithm = new RijndaelManaged();
algorithm.Mode = CipherMode.CBC;
algorithm.Padding = PaddingMode.PKCS7;
algorithm.BlockSize = 128;
algorithm.KeySize = 128;
algorithm.Key = key;
algorithm.IV = iv;

// Create a new encryptor and encrypt the given value
ICryptoTransform cipher = algorithm.CreateEncryptor();
byte[] output = cipher.TransformFinalBlock(input, 0, input.Length);

// Finally, return the encrypted value in base64 format
String encrypted = Convert.ToBase64String(output);

return encrypted;
}

public static String DecryptText(String Data, String Key, String IV)
{
// Extract the bytes of each of the values
byte[] input = Convert.FromBase64String(Data);
byte[] key = Convert.FromBase64String(Key);
byte[] iv = Convert.FromBase64String(IV);


// Create a new instance of the algorithm with the desired settings
RijndaelManaged algorithm = new RijndaelManaged();
algorithm.Mode = CipherMode.CBC;
algorithm.Padding = PaddingMode.PKCS7;
algorithm.BlockSize = 128;
algorithm.KeySize = 128;
algorithm.Key = key;
algorithm.IV = iv;

//FromBase64String
// Create a new encryptor and encrypt the given value
ICryptoTransform cipher = algorithm.CreateDecryptor();
byte[] output = cipher.TransformFinalBlock(input, 0, input.Length);

// Finally, convert the decrypted value to UTF8 format
String decrypted = Encoding.UTF8.GetString(output);

return decrypted;
}
}
}

Hopefully these small tips will help someone else or at least keep them from pulling out their hair trying to coerce ColdFusion and some other tool into producing the same results.

10 comments:

Unknown December 16, 2009 at 10:18 AM  

Thanks! This was very helpful. You helped me figure out a bug in my code that had me blocked for hours.

cfSearching December 16, 2009 at 12:31 PM  

@Josh,

Having spent a lot of time on it myself, I am glad it helped ;)

-Leigh

Anonymous,  January 5, 2010 at 1:04 AM  

Thank you, this was just what I needed. Don't think I could've done it without your examples.

dinger April 28, 2010 at 5:58 AM  

Thanks - this has been a great post. However, I am having great trouble getting my code to work. I have tried your code, and still no dice. The ColdFusion code I am working with is as follows -

#Hash(Encrypt(Form.UserPassword,GetSiteVars.EnCode))#

Correct me if I'm wrong, but this is using a key to encrypt the password, but no IV or anything else. How, then, do I know what use?

Thanks

cfSearching April 28, 2010 at 12:17 PM  

@dinger,

This post is specifically about the AES algorithm. It does not apply in your case because you are using the default algorithm: CFMX_COMPAT (also the weakest encryption)

Correct me if I'm wrong, but this is using a key to encrypt the password, but no IV or anything else. How, then, do I know what use?

Yes, CF uses your key (ie GetSiteVars.Encode) as a "seed" to generate a random key internally. No IV or other parameters are required. CF then runs some sort of XOR algorithm internally to create the encrypted string. To duplicate the _encrpt_ result in C#, you would need to know how CF performs the XOR. I am not sure.

But why use CFMX_COMPAT algorithm at all? A one way md5 Hash() should be sufficient.. and you can definitely duplicate that in C#.

HTH
-Leigh

dinger April 28, 2010 at 12:23 PM  

yeah - that's what I figured. I have been having quite a time getting this to work.

Unfortunately, this is a supported application and I can't change the encryption scheme - I am just trying to decrypt it.

Thanks

cfSearching April 28, 2010 at 1:34 PM  

@dinger,

From what I understand, the value you pass into encrypt is just a seed. Not the actual key used for the XOR. If you could figure out it how it generates the key .. maybe you could duplicate it.

Assuming you really _have_ to do this that is..

-Leigh

cfSearching April 28, 2010 at 2:54 PM  

You might also take a look at the Railo source. It is java, but supports the CFMX_COMPAT algorithm. So you could port it to C#. Just check the Railo licensing stuff of course ...

-Leigh

cfSearching May 1, 2010 at 12:09 AM  

@dinger,

Disclaimer: Bit math is not my strong suit. But I think I got a C# port of the Railo code to work. If you are interested, email me at cfsearching at either yahoo or gmail.

-Leigh

Anonymous,  June 17, 2010 at 6:44 AM  

Thanks, helped me a lot!

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep