4.38 Review Encryption and Decryption Mistakes
4.38 - Review Encryption and Decryption Mistakes
Encryption and decryption mistakes appear when data is protected with weak algorithms, static IVs, missing authentication, or keys stored alongside ciphertext. Review field-level encryption, token wrapping, file protection, and database column encryptors. Confirm each use case needs confidentiality, integrity, or both—and that the implementation matches.
What This Vulnerability Is
Encryption protects confidentiality; without authentication, attackers may tamper with ciphertext or perform padding oracle attacks. Common mistakes include AES-ECB for structured data, reusing IVs with GCM, deriving keys from passwords without KDFs, storing keys next to ciphertext, and confusing encoding (Base64) with encryption. Decryption endpoints may also become oracle surfaces when they return distinct errors for bad padding versus bad data.
The unsafe assumption is that any encryption call makes data safe. Correct designs choose AEAD modes, manage keys in HSMs or vaults, rotate material, and separate duties between encrypting systems and key custodians. Hashing is for verification and password storage; it does not replace encryption when reversible access is required. This pattern relates to CWE-311 (Missing Encryption of Sensitive Data) and CWE-326 (Inadequate Encryption Strength).
Vulnerability Characteristics (Where to Identify Them)
| Signal | Where to look |
|---|---|
| Feature type | Field-level encryption, token wrapping, PII tokenization, backup protection, file at rest |
| Weak modes | AES-ECB, DES, 3DES, RC4, CBC without HMAC |
| IV/nonce issues | Hardcoded IV arrays, counters reset on restart, GCM nonce reuse |
| Key handling | Keys in source, config in git, keys stored beside ciphertext in the same database |
| Missing authentication | Encrypt-then-none patterns without GCM or encrypt-then-MAC |
| Oracle behavior | Decryption endpoints returning different errors for padding vs format failures |
| Wrong primitive | Reversible encryption of passwords instead of adaptive hashing |
Attack Payloads
Use these in authorized cryptographic review and lab testing—not against production decryption endpoints without approval.
Pattern 1: Padding oracle probing (CBC)
# Send modified ciphertext blocks; observe distinct error responses:
# "bad padding" vs "invalid format" vs HTTP 500 timing differences
# Each distinguishable response may leak one plaintext byte
Pattern 2: ECB pattern analysis
# Encrypt repeated 16-byte blocks; identical ciphertext blocks reveal structure
# Example: identical blocks in encrypted JSON with repeated keys
{"user":"admin","role":"admin"} # repeated "admin" blocks align in ECB
Pattern 3: GCM nonce reuse
# Reusing (key, nonce) pair with different plaintexts breaks GCM confidentiality
# Attackers XOR ciphertexts to derive plaintext XOR relationships
Pattern 4: Static IV exploitation
# Same IV + same key + different messages → deterministic ciphertext prefixes
# Enables equality leaks and some chosen-plaintext analysis
Pattern 5: Hardcoded key from source leak
# Fernet key committed in settings.py: FIELD_KEY = b'abc123xyz7890123456789012345678='
# Decrypt all historical field ciphertext after repo fork or leak
grep -r "FIELD_KEY" config/
Language-Specific Sinks and Dangerous APIs
Python
AES.new(KEY, AES.MODE_ECB)
AES.new(KEY, AES.MODE_CBC, iv=STATIC_IV) # no HMAC
from Crypto.Cipher import DES3
Fernet(HARDCODED_KEY)
hashlib.pbkdf2_hmac(..., iterations=1000) # too low
cryptography.hazmat... without AEAD
base64.b64encode(data) # mistaken for encryption
Also review: pycryptodome without authentication tag, password-based keys without salt/KDF.
Java
Cipher.getInstance("AES/ECB/PKCS5Padding");
Cipher.getInstance("DES/ECB/PKCS5Padding");
Cipher.getInstance("AES/CBC/PKCS5Padding"); // no GCM/HMAC
SecretKeySpec(hardcodedBytes, "AES");
IvParameterSpec(staticIv);
PBEWithMD5AndDES
BadPaddingException caught and rethrown with distinct message
C
Aes.Create(); aes.Mode = CipherMode.ECB;
Aes.Create(); aes.Mode = CipherMode.CBC; // no GCM
RijndaelManaged with static IV
ProtectedData.Protect with hardcoded entropy
Convert.ToBase64String(plaintext) // encoding only
CryptographicException message leaked to client
Go
aes.NewCipher(key) // manual ECB loop
cipher.NewCBCEncrypter(block, staticIV)
des.NewCipher(key)
// missing cipher.NewGCM for authenticated encryption
errors.New("bad padding") returned to HTTP client
C
AES_ecb_encrypt(...);
AES_cbc_encrypt(..., iv, AES_ENCRYPT); // static iv
DES_ncbc_encrypt(...);
EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), ...);
SQL / config
-- Key stored beside ciphertext in same row
UPDATE users SET encrypted_ssn = ..., encryption_key = '...' WHERE id = 1;
Sample Vulnerable Code in Python
from Crypto.Cipher import AES
KEY = b"0123456789012345" # hardcoded key in source
def encrypt(data: bytes) -> bytes:
cipher = AES.new(KEY, AES.MODE_ECB)
return cipher.encrypt(pad(data))
def decrypt(token: bytes) -> bytes:
try:
return unpad(AES.new(KEY, AES.MODE_ECB).decrypt(token))
except ValueError:
# Distinct error reveals padding validity to attackers
raise BadPaddingError("invalid padding")
Step-by-Step Review Walkthrough
- Map each encryption use case. Identify data at rest, in transit, token wrapping, PII tokenization, and backup protection flows.
- Identify algorithm and mode. Prefer AES-GCM or ChaCha20-Poly1305; flag DES, RC4, and AES-ECB.
- Check IV or nonce generation. Nonces must be unique per message with GCM; static IVs break confidentiality.
- Verify authentication. Ciphertext should include MAC or use AEAD; encryption alone does not provide integrity.
- Trace key storage. Keys should not live in source, git config, or the same database row as ciphertext without access controls.
- Review password-based encryption. Use PBKDF2, scrypt, or Argon2 with high work factors before deriving keys.
- Inspect decryption error handling. Avoid distinguishable error messages that leak padding validity to attackers.
Risk Impact Analysis
Ciphertext tampering. Without authentication, attackers may alter encrypted fields, tokens, or files without detection.
Padding oracle attacks. Distinct decryption errors on CBC-mode ciphertext may leak plaintext byte by byte.
Key compromise from source leaks. Hardcoded keys in repositories expose all historical ciphertext when the repo is copied or forked.
Nonce reuse in GCM. Reusing a GCM nonce with the same key catastrophically breaks confidentiality and integrity.
Regulatory exposure. Unencrypted or weakly encrypted PII and payment data fail PCI, HIPAA, and GDPR technical control expectations.
Vulnerable Examples in Other Languages
Java
private static final byte[] KEY = "0123456789012345".getBytes(StandardCharsets.UTF_8);
public byte[] encrypt(byte[] plaintext) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec spec = new SecretKeySpec(KEY, "AES");
cipher.init(Cipher.ENCRYPT_MODE, spec);
return cipher.doFinal(plaintext);
}
public byte[] decrypt(byte[] ciphertext) throws Exception {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(KEY, "AES"));
return cipher.doFinal(ciphertext);
} catch (BadPaddingException e) {
throw new BadPaddingError("invalid padding"); // distinguishable error
}
}
private static final byte[] IV = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
C
private static readonly byte[] Key = Encoding.UTF8.GetBytes("0123456789012345");
public string Encrypt(string plain)
{
using var aes = Aes.Create();
aes.Key = Key;
aes.Mode = CipherMode.ECB;
using var enc = aes.CreateEncryptor();
return Convert.ToBase64String(enc.TransformFinalBlock(
Encoding.UTF8.GetBytes(plain), 0, plain.Length));
}
public string Decrypt(string token)
{
try {
/* ECB decrypt */
} catch (CryptographicException) {
throw new BadPaddingException("invalid padding"); // leaks validity to callers
}
}
Go
var appKey = []byte("sixteen-byte-key")
func Encrypt(data []byte) []byte {
block, _ := aes.NewCipher(appKey)
ciphertext := make([]byte, len(data))
block.Encrypt(ciphertext, data) // ECB-style block loop without authentication
return ciphertext
}
func Decrypt(token []byte) ([]byte, error) {
block, _ := aes.NewCipher(appKey)
out := make([]byte, len(token))
block.Decrypt(out, token)
if !validPadding(out) {
return nil, errors.New("bad padding") // oracle-friendly error
}
return out, nil
}
Fix: Safer Patterns and Libraries to Use
Python
Use AES-GCM with random nonces and keys from environment or KMS.
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def get_key() -> bytes:
return bytes.fromhex(os.environ["DATA_ENCRYPTION_KEY"])
def encrypt(plaintext: bytes) -> bytes:
key = get_key()
nonce = os.urandom(12)
ciphertext = AESGCM(key).encrypt(nonce, plaintext, associated_data=None)
return nonce + ciphertext
def decrypt(blob: bytes) -> bytes:
key = get_key()
nonce, ciphertext = blob[:12], blob[12:]
try:
return AESGCM(key).decrypt(nonce, ciphertext, associated_data=None)
except Exception:
raise ValueError("decryption failed") # generic error to callers
See cryptography AESGCM and Fernet for opinionated token formats.
Java
Use AES/GCM with SecureRandom IVs per operation. Wrap data keys with KMS.
public byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception {
byte[] iv = new byte[12];
SecureRandom.getInstanceStrong().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] ciphertext = cipher.doFinal(plaintext);
return ByteBuffer.allocate(iv.length + ciphertext.length)
.put(iv).put(ciphertext).array();
}
Integrate AWS KMS or Google Cloud KMS for envelope encryption.
C
Use AesGcm with proper nonce handling and Key Vault for key storage.
public byte[] Encrypt(byte[] plaintext, byte[] key)
{
var nonce = RandomNumberGenerator.GetBytes(12);
var ciphertext = new byte[plaintext.Length];
var tag = new byte[16];
using var aes = new AesGcm(key, tag.Length);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
return nonce.Concat(ciphertext).Concat(tag).ToArray();
}
Use ASP.NET Core Data Protection for key ring management and rotation.
Go
Use crypto/cipher.NewGCM with random nonces from crypto/rand.
func Encrypt(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
See crypto/cipher NewGCM and age for higher-level file encryption.
Verify During Review
- Sensitive data at rest and in tokens uses modern AEAD with unique nonces per encryption operation.
- Keys are stored in KMS, HSM, or secret managers—not in source control or client bundles.
- Passwords are hashed with adaptive algorithms; reversible encryption is reserved for data that must be read back.
- Decryption failures return generic errors to callers; logs do not leak padding or oracle details to untrusted users.
- Key rotation and access logging exist for production encryption keys.
- Encoding (Base64, hex) is not mistaken for encryption in design documents or variable names.
Reference
- CWE-311: Missing Encryption of Sensitive Data
- CWE-326: Inadequate Encryption Strength
- CWE-347: Improper Verification of Cryptographic Signature
- NIST SP 800-38D: GCM Mode
- OWASP Cryptographic Storage Cheat Sheet
- Python cryptography AESGCM
- Java Cipher AES/GCM/NoPadding
- ASP.NET Core AesGcm
- Go crypto/cipher NewGCM