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.
...Read More