The last lesson was about data you never need back: a password, hashed one-way. This lesson is the opposite case, data you do need to read again later, like a stored API credential or a sensitive database column. That's what encrypt() and decrypt() are for, and getting the algorithm, key, and initialization vector right matters just as much as choosing to encrypt in the first place.
Learning Objectives
After completing this lesson, you'll be able to:
- Encrypt and decrypt a string with encrypt()/decrypt(), matching every parameter between the two calls.
- Generate a real cryptographic key with generateSecretKey() instead of hardcoding a string.
- Use an initialization vector (IV) correctly with CBC mode, and know when a salt is used instead.
- Use encryptBinary()/decryptBinary() for raw binary data.
- Know a real, current difference in how Adobe ColdFusion and Lucee handle the old CFMX_COMPAT algorithm.
How Two-Way Encryption Fits Together
Sensitive Data
data you need to read back later
generateSecretKey() + encrypt()
AES/CBC/PKCS5Padding + IV
Store the Ciphertext
unreadable without the key
decrypt() When Needed
same key, algorithm, and IV required
The Core Functions
encrypt() and decrypt() share the same parameter list. Whatever algorithm, encoding, and IV or salt you encrypt with, decrypt() needs the exact same values back, along with the same key, or it can't reverse the operation.
secretKey = generateSecretKey("AES");
secret = "top secret";
encrypted = encrypt(secret, secretKey, "AES/CBC/PKCS5Padding", "Base64");
decrypted = decrypt(encrypted, secretKey, "AES/CBC/PKCS5Padding", "Base64");
writeOutput(decrypted); // top secretSignature: encrypt(string, key [, algorithm, encoding, iv | salt, iterations]) and decrypt(string, key [, algorithm, encoding, iv | salt, iterations]), same parameters in the same order for both.
A Real Discrepancy: CFMX_COMPAT's Status Differs by Engine
CFMX_COMPAT was ColdFusion's original encrypt()/decrypt() algorithm, an XOR-based cipher with a 32-bit key, and it used to be the default when no algorithm was specified. ColdFusion (2023 release) Update 8 and ColdFusion (2021 release) Update 14 changed the default to AES/CBC/PKCS5Padding specifically because CFMX_COMPAT is weak. Adobe went further in the ColdFusion 2025 release: it removed CFMX_COMPAT support entirely, so code still specifying it outright fails on that version rather than just running insecurely.
Lucee's own documentation, by contrast, still lists CFMX_COMPAT as its default algorithm as of this writing, while explicitly warning that it "is not cryptographically secure" and recommending AES instead. If a project needs to run on both engines, don't rely on the default at all, pass AES/CBC/PKCS5Padding (or another modern algorithm) explicitly every time.
Generating a Real Key
A hardcoded string used as a key is a weak key, it's not generated with real cryptographic randomness. generateSecretKey(algorithm [, keysize]) produces one that is.
// defaults to a 128-bit key if keysize is omitted
key128 = generateSecretKey("AES");
// an explicit 256-bit key
key256 = generateSecretKey("AES", 256);128-bit AES keys are considered sufficient for current use. A 256-bit key isn't meaningfully more secure against any practical attack, but costs noticeably more CPU time to use, worth knowing before defaulting to 256 just because the number is bigger.
Using an Initialization Vector with CBC Mode
CBC mode needs an initialization vector (IV) so that encrypting the same plaintext twice doesn't produce identical ciphertext. The IV has to be a binary value the exact same size as the algorithm's block size, 16 bytes for AES, and it's passed in the same parameter slot a password-based algorithm would use for a salt, the two are mutually exclusive.
| Parameter | Used With | Purpose |
|---|---|---|
| iv | Block modes like AES/CBC/PKCS5Padding | Makes repeated encryption of the same input produce different ciphertext |
| salt | Password-based encryption (PBE) algorithms | Combined with iterations to derive the actual encryption key from a password |
A Real Example: When You Actually Want Repeatable Ciphertext
CBC's randomized output is usually exactly what you want, but it breaks a specific use case: looking up a row by an encrypted column's value (WHERE encrypted_email = :value), since the same plaintext never encrypts to the same ciphertext twice. For that narrow case, ECB mode with multiple encryption passes produces a repeatable result you can actually match against in a WHERE clause, at the cost of ECB's weaker guarantees for everything else. It's a deliberate, narrow trade-off, not a default choice.
Binary Data: encryptBinary() / decryptBinary()
encrypt()/decrypt() work on text strings. For raw binary data (a file's bytes, for instance), encryptBinary()/decryptBinary() do the same job without a text-encoding step in between.
encryptedBytes = encryptBinary(fileBytes, secretKey); originalBytes = decryptBinary(encryptedBytes, secretKey);
decryptBinary()'s third parameter is named prefix rather than iv, it serves the same purpose (the IV or salt used during encryption), just under a different parameter name than the text-based functions use.
Common Beginner Mistakes
Encrypting a password instead of hashing it
Encryption is two-way, if the key is ever compromised, every password encrypted with it is immediately readable. Passwords should be hashed one-way with PasswordHashGenerate() or Argon2Hash(), covered in the previous lesson, never encrypted.
Decrypting with different parameters than were used to encrypt
Algorithm, encoding, and the IV or salt all have to match exactly between the encrypt() and decrypt() calls. A mismatch in any one of them fails, since decrypt() can't reverse an operation it wasn't given the exact settings for.
Relying on the default algorithm instead of specifying one explicitly
The default differs by ColdFusion version and even by engine, CFMX_COMPAT used to be the default and is now removed entirely on ColdFusion 2025, while Lucee still defaults to it. Always pass the algorithm explicitly.
Reusing the same IV for every encryption call
The entire point of an IV is that it's unique per encryption. Reusing one defeats its purpose and can leak information about the plaintext across multiple encrypted values.
Best Practices
- Always specify the algorithm explicitly, don't rely on the engine's default.
- Generate keys with generateSecretKey(), never hardcode a string as a key.
- Use a fresh, random IV for each encryption call under CBC mode.
- Reach for ECB with multiple passes only for the narrow case of needing to search by an encrypted column's value, not as a general default.
Interview Questions
What's the fundamental difference between how you'd protect a password versus a stored credit card number?
A password is hashed one-way, there's no legitimate reason to ever recover the original value. A credit card number (or any data you need to read back later) is encrypted two-way with encrypt()/decrypt(), which requires a key capable of reversing the operation.
Why does CBC mode need an initialization vector?
Without one, encrypting the same plaintext twice with the same key produces identical ciphertext, which leaks information about the data. The IV makes each encryption's output unique even for identical input.
Why would CFMX_COMPAT-encrypted data created on an older ColdFusion server fail to decrypt on ColdFusion 2025?
ColdFusion 2025 removed support for the CFMX_COMPAT algorithm entirely, it isn't just discouraged, calls that specify it fail outright on that version.
Summary
In this lesson, you encrypted and decrypted a string with matching parameters, generated a real key with generateSecretKey(), used an IV correctly under CBC mode and learned when a salt is used instead, handled binary data with encryptBinary()/decryptBinary(), and saw a real current difference in how Adobe ColdFusion (CFMX_COMPAT removed in 2025) and Lucee (CFMX_COMPAT still the default, but discouraged) handle the same legacy algorithm.
What's Next?
The next lesson covers secure sessions: keeping session identifiers safe from hijacking, and configuring session cookies and timeouts correctly.