AES, RSA, DES Encryption Explained: What Actually Matters in Production
AES, RSA, DES, ECC, ChaCha20, post-quantum cryptography explained with real production trade-offs, code examples, key management lifecycle, and the mistakes that get systems pwned.
N
Naren · Founder
Plain-English first. Then code. Then the interview question.
AES-GCM is the default for new projects: authenticated encryption with integrity checks.
RSA is for wrapping ephemeral keys, not encrypting payloads directly. Use OAEP padding, never PKCS#1 v1.5.
ECC (ECDH, ECDSA) provides equivalent security with much smaller keys. P-256 is the safe default.
ChaCha20-Poly1305 is faster than AES-GCM on devices without hardware acceleration.
Nonce reuse breaks GCM and ChaCha20 completely. Use SecureRandom, never counters or timestamps.
Key management is the most common failure: use envelope encryption with a KMS.
Post-quantum: ML-KEM and ML-DSA are NIST standards. Start hybrid migration now.
Plain-English First
Imagine you run a postal service. Symmetric encryption (AES) is like a lockbox where you and your friend both have identical keys fast, efficient, great for heavy packages. Asymmetric encryption (RSA) is like a mail slot on your front door: anyone can drop a letter in (encrypt with your public key), but only you have the key to open the box (private key). DES is the lockbox your grandfather used in 1977 the key is so short a determined thief can try every possible combination over a weekend. ECC is a smaller, smarter lock that gives you the same security as a massive RSA lock in a fraction of the size. And post-quantum cryptography is the lock designed to survive the day someone builds a quantum computer powerful enough to pick all the locks we use today. The whole modern cryptography stack is just figuring out which box to use, when, and how not to leave the key taped to the lid.
A fintech startup I consulted for was encrypting customer PII with DES in 2019. Not 3DES. Plain, single DES. The dev who wrote it had copy-pasted a Stack Overflow answer from 2004, the code passed every code review because nobody checked the cipher string, and it sailed into production for three years. When a penetration tester cracked a sample in under four hours on a laptop, the incident report was brutal. The kicker? The fix was literally changing one string from 'DES/ECB/PKCS5Padding' to 'AES/GCM/NoPadding'. Three years of exposure from one lazy string.
Encryption is the one area of software engineering where 'good enough' is catastrophically different from 'correct'. A slightly inefficient database query costs you milliseconds. A slightly broken cipher costs you your users' data, your compliance certifications, and potentially your company. The gap between AES-GCM and AES-ECB isn't academic ECB mode leaks structural patterns in your plaintext, meaning an attacker can detect when two encrypted values are identical without ever decrypting them. RSA with PKCS#1 v1.5 padding versus OAEP isn't a footnote PKCS#1 v1.5 is vulnerable to Bleichenbacher's attack, a padding oracle exploit that has broken real TLS implementations in production. These distinctions are not theoretical.
After reading this you'll be able to make a deliberate, defensible algorithm and mode choice for a new service, read a cipher string like 'AES/GCM/NoPadding' and know exactly what each segment means and why it matters, spot the three most common encryption anti-patterns in a code review, and have a clear mental model for when to use symmetric versus asymmetric encryption and when to combine them, which is what every TLS handshake on the planet does. We'll also cover ECC as the modern replacement for RSA, post-quantum migration planning, ChaCha20-Poly1305 for non-hardware-accelerated platforms, key management lifecycle, encrypted search patterns, and the gritty details of side-channel attacks.
DES and 3DES: Why 56 Bits Gets You Fired in 2026
DES the Data Encryption Standard was standardised by NIST in 1977. Its fatal flaw isn't the algorithm design itself, it's the key size: a mere 56 effective bits. With 2^56 possible keys (roughly 72 quadrillion combinations), that sounds enormous until you realise dedicated hardware can now exhaust the entire keyspace in hours, not days. The EFF's Deep Crack machine broke DES in 22 hours back in 1998 on 1998 hardware. Today, with a modest budget for cloud GPU instances, you're talking minutes. Forget it.
3DES (Triple DES) was the duct-tape fix: apply DES three times with different keys, effectively getting 112 bits of security. It worked as a stopgap and you'll still find it in payment processing systems that haven't been migrated, a stark reminder of PCI DSS's glacial pace (they only formally deprecated 3DES for new implementations in 2023). But 3DES is slow three cipher passes per block and its 64-bit block size creates a birthday attack vulnerability called SWEET32. Once you encrypt roughly 32GB of data under the same key, the birthday bound means you're likely to have duplicate plaintext blocks, allowing certain attacks. In a high-throughput API, you can hit that ceiling in a single day.
Don't use either in new code. Ever. If you're maintaining legacy code that uses them, that migration ticket is a P1 security item, not a 'nice to have'. Treat it like a critical SQL injection vulnerability. The technical debt is immense, and the risk is unacceptable.
Here's the painful part: migrating legacy encryption isn't just changing a cipher string. You need to identify all encrypted columns, decrypt with old key, re-encrypt with new, and rotate credentials all without downtime. We did it for a healthcare API using a dual-write strategy: write new records with AES-GCM, keep reading old records with 3DES, then backfill during off-peak hours. Took six months. Worth every minute.
Using 'DES/ECB/PKCS5Padding' on structured data like credit card numbers or user IDs means identical plaintexts produce identical ciphertexts. An attacker querying your encrypted column can detect which users share the same password hash equivalent without ever breaking the encryption. ECB mode is encryption theatre. If you see it in a codebase, treat it as a confirmed vulnerability, not a code smell. The same applies to AES-ECB.
Production Insight
One team encrypted all PII with DES/ECB because 'it was the default'. A penetration tester cracked 90% of SSNs in 4 hours by matching ciphertext patterns.
The fix: change to AES/GCM/NoPadding, re-encrypt all data, rotate keys.
Rule: If your encryption mode is ECB, you have no encryption you have obfuscation.
Key Takeaway
DES is broken since 1998 (EFF Deep Crack). 3DES is vulnerable to SWEET32 after ~32GB.
Use AES-256-GCM for all symmetric encryption. Never use ECB mode.
If you find DES/3DES in code, it's a P1 security incident not a refactoring task.
Is your data encrypted with DES or 3DES?
IfYou're writing new code
→
UseStop. Use AES-256-GCM or ChaCha20-Poly1305. Never DES/3DES.
IfLegacy 3DES data exists, < 32GB per key
→
UsePriority migration. Map dual-write strategy and backfill.
UseTreat as confirmed vulnerability. Schedule P1 ticket.
AES-GCM: The Workhorse for Modern Confidentiality and Integrity
AES (Advanced Encryption Standard) has been the cryptographic gold standard since NIST selected Rijndael in 2001. It supports 128, 192, and 256-bit keys and operates on fixed 128-bit blocks. The algorithm itself is robust; the trap lies almost exclusively in the mode of operation.
AES-ECB (Electronic Codebook) is what you use if you want your encryption to actively, and literally, leak information about your data. It encrypts each block independently. If you encrypt two identical blocks of data, you get two identical ciphertexts. This reveals patterns. AES-CBC (Cipher Block Chaining) is better; it's deterministic encryption for confidentiality. However, it only provides confidentiality. It doesn't tell you if the ciphertext was tampered with. This is where padding oracle attacks (POODLE, Lucky 13) exploit weaknesses. If an attacker can make your server reveal whether padding is correct or not, they can often decrypt arbitrary messages. Modern systems demand authenticated encryption, and that's where AES-GCM shines.
AES-GCM (Galois/Counter Mode) is what you should be using for symmetric encryption. It's an AEAD (Authenticated Encryption with Associated Data) mode, meaning it guarantees both confidentiality and integrity. If someone flips a single bit in your ciphertext, GCM decryption throws a javax.crypto.AEADBadTagException instead of silently handing you corrupted plaintext. That's not a nice-to-have; it's the difference between detecting an attack and processing fraudulent data. For databases, file encryption, or any data-at-rest scenario where you need to trust the data hasn't been modified, AES-256-GCM is your go-to.
GCM has one critical footgun:never reuse a nonce (Number Used Once) under the same key. Nonce reuse doesn't just weaken GCM; it catastrophically collapses its security. An attacker who observes two ciphertexts encrypted with the same key and nonce can XOR them together to cancel out the keystream and recover the plaintext of both messages. This has happened in real-world systems, notably in the Azure cloud. Always generate nonces with a cryptographically secure random number generator (SecureRandom in Java). Prepend the nonce to the ciphertext, and derive it from the blob during decryption. Never generate it from a counter you're storing in a database without robust distributed coordination, as even a simple failover can cause counter resets and collisions.
Then there's AAD Associated Data. Both AES-GCM and ChaCha20-Poly1305 support it, and you should use it. AAD is data that's not encrypted but is authenticated. Bind metadata (user ID, record type, timestamp) to the ciphertext. If an attacker copies an encrypted blob from one user's record to another's, the AAD mismatch causes decryption to fail. It's free, requires no extra storage, and catches an entire class of semantic attacks that pure ciphertext encryption misses.
And here's a trap I've seen in practice: someone implemented AES-GCM with nonce derived from a database sequence. When the DB was restored from backup, the sequence reset and boom, nonce reuse. They encrypted 200K records before noticing. The fix was using random nonces and key versioning. The lesson: never derive nonces from anything restartable.
Performance-wise, AES-GCM on modern x86 servers with AES-NI can encrypt at 1+ GB/s per core. Without AES-NI, throughput drops to 50-100 MB/s comparable to ChaCha20's software speed. That's why ChaCha20 exists.
Production Trap: Nonce Reuse Destroys GCM Security Completely
I saw a team implement AES-GCM with a nonce derived from a database auto-increment ID. When they restored a backup and the ID counter reset to a previous value, they started reusing nonces for new data encrypted under the same key. Two messages encrypted with the same key+nonce in GCM can be XORed together to recover both plaintexts the encryption is completely broken, not merely weakened. Always generate nonces with SecureRandom. Never derive them from any predictable or resettable source.
Production Insight
A cloud backup tool reused nonces across restorations because of a counter reset. Millions of records became decryptable by the attacker.
Always generate nonces with SecureRandom. Never use auto-increment IDs or timestamps.
The AEADBadTagException is your friend log it, alert on it, never ignore it.
Key Takeaway
AES-GCM provides both confidentiality and integrity (tamper detection).
Never reuse a nonce under the same key it collapses GCM security.
Use random nonces, prepend to ciphertext. Always version your keys.
Choosing between AES-GCM and ChaCha20-Poly1305
IfYour platform x86-64 with AES-NI? (check /proc/cpuinfo | grep aes)
UseUse ChaCha20-Poly1305 faster in software, constant time.
IfYou need FIPS 140-2/140-3 compliance?
→
UseUse AES-GCM. ChaCha20 not yet in FIPS modules.
IfProtocol design from scratch, want simplicity?
→
UseChaCha20-Poly1305: harder to get wrong, constant-time by design.
IfExisting codebase with AES-GCM but hitting performance without HW acceleration?
→
UseSwitch to ChaCha20-Poly1305 for that target.
ChaCha20-Poly1305: The AEAD Cipher When AES-NI Isn't Available
AES-GCM is the default choice for symmetric encryption on modern server hardware. But that qualifier 'modern server hardware' matters more than most engineers realize. AES-GCM's speed depends on AES-NI (AES New Instructions), a set of CPU instructions available on x86-64 processors since ~2010 and on newer ARM chips (ARMv8 Cryptography Extensions). Without AES-NI, AES-GCM falls back to a software implementation that is dramatically slower we're talking 10-20x slower, not 10-20% slower.
This matters for: mobile devices (older Android phones, especially ARMv7 devices without crypto extensions), embedded systems and IoT devices with limited CPU, cloud instances on non-x86 architectures (Graviton ARM instances are fast for AES if they have extensions, but not all do), and any environment where you can't guarantee hardware acceleration.
ChaCha20-Poly1305 was designed by Daniel J. Bernstein specifically to be fast in software without any hardware acceleration. It's a stream cipher (ChaCha20) combined with a MAC (Poly1305), giving you the same AEAD guarantee as AES-GCM: confidentiality plus integrity in one operation. Google adopted it for HTTPS in 2014 after measuring that it outperformed AES-GCM on Android devices by a significant margin. It's now in TLS 1.3 as a standard cipher suite, used by WireGuard, SSH, and Android's file-based encryption.
Key differences from AES-GCM: - ChaCha20 uses a 256-bit key and 96-bit nonce (same nonce size as GCM). - Poly1305 produces a 128-bit authentication tag (same as GCM's tag). - The nonce reuse rule is identical: never reuse a nonce under the same key. The consequences are similar keystream reuse allows plaintext recovery. - ChaCha20 has a simpler, more constant-time-friendly design. AES has known cache-timing vulnerabilities in software implementations; ChaCha20 doesn't use table lookups, so it's naturally resistant to cache-timing attacks.
When to choose which: - AES-256-GCM: default for server-side Java on x86-64 with AES-NI. Fastest option with hardware support. - ChaCha20-Poly1305: mobile clients, embedded systems, ARM without crypto extensions, or when you want a cipher with a simpler constant-time profile. Also preferred if you're building a protocol from scratch and want to avoid AES's complexity. - In Java: ChaCha20-Poly1305 is available since Java 11 (ChaCha20-Poly1305 transformation in JCA). Bouncy Castle also provides it.
A real-world data point: an IoT firmware update service using AES-GCM on ARM Cortex-M0 chips without AES-NI took 200ms per packet to decrypt. Switching to ChaCha20-Poly1305 brought it down to 15ms. That's the difference between a usable product and a brick.
Both AES-GCM and ChaCha20-Poly1305 support Associated Data (AAD) data that isn't encrypted but is authenticated. Use it to bind metadata (user ID, record type, timestamp) to the ciphertext. If an attacker copies an encrypted blob from one user's record to another's, the AAD mismatch causes decryption to fail. It's free, requires no extra storage, and catches an entire class of attacks that pure ciphertext encryption misses.
Production Insight
An IoT firmware update service used AES-GCM on ARM Cortex-M0 chips without AES-NI. Decryption took 200ms per packet, causing timeouts.
Switched to ChaCha20-Poly1305. Decryption dropped to 15ms per packet.
Rule: Always check for AES-NI support. If absent, ChaCha20 is your tool.
Key Takeaway
ChaCha20-Poly1305 is as secure as AES-GCM but faster in software without hardware acceleration.
Use it for mobile, IoT, or any platform without AES-NI.
Nonce reuse rules apply identically: never reuse a nonce under the same key.
RSA: The Unsung Hero of Key Exchange, a Villain in Direct Encryption
RSA solves a problem that symmetric encryption (like DES and AES) can't: secure key distribution. With symmetric encryption, both parties need the same secret key but how do you securely share that key in the first place? You can't encrypt it, because you don't have a shared key yet. RSA (Rivest Shamir Adleman, 1977) breaks this deadlock with a mathematically linked key pair: a public key you can share with the world, and a private key that never leaves your server.
The math rests on the difficulty of factoring the product of two large prime numbers. If I multiply two 2048-bit primes together, the resulting number is your RSA modulus. Deriving the private key from the public key requires factoring that modulus back into its primes a problem that has no known efficient classical algorithm. Quantum computers with sufficient qubits could break this via Shor's algorithm, which is why NIST is actively standardizing post-quantum replacements.
Here's the production reality many engineers miss: RSA is slow. RSA-2048 encryption is roughly 1000x slower than AES-256. You never, ever use RSA to encrypt bulk data. You use it to encrypt a randomly generated AES session key, then use that AES key for the actual payload. This is precisely what TLS does during its handshake. RSA-OAEP (Optimal Asymmetric Encryption Padding) is the padding scheme you must use. PKCS#1 v1.5 is vulnerable to Bleichenbacher's padding oracle attack a classic exploit that has broken real-world TLS implementations and is still found in legacy systems. Don't be that team.
Another trap: key size. Since JDK 8u301 and JDK 11.0.11, the JVM's default security policy disallows RSA keys below 1024 bits, throwing InvalidKeyException at runtime. NIST has considered 512-bit RSA broken since 2010. Use 2048-bit minimum for anything active; 4096-bit for certificate authorities or keys with 10+ year lifetimes. If you're still using RSA-1024 in production today, it's a ticking compliance bomb.
And one more thing: if you're using RSA for signing, use PSS padding, not PKCS#1 v1.5. The latter has known weaknesses for signature schemes too. Most crypto libraries default to PSS these days, but double-check.
Performance comparison: RSA-2048 encrypt is ~10,000 ops/sec on a modern core, while AES-256-GCM hits 1M+ ops/sec. That's why hybrid encryption is non-negotiable.
Elliptic Curve Cryptography (ECC): Smaller Keys, Same Security, Less Headroom
Elliptic Curve Cryptography (ECC) is the modern evolution of asymmetric encryption. It offers equivalent security to RSA but with much smaller key sizes a 256-bit ECC key is roughly equivalent to a 3072-bit RSA key. That means faster operations, less bandwidth, and smaller signatures. ECC is the backbone of modern TLS (ECDHE, ECDSA), Bitcoin (secp256k1), and SSH (Ed25519).
There are two main ECC primitives you'll encounter
ECDH (Elliptic Curve Diffie-Hellman) key exchange, used in TLS 1.3 for perfect forward secrecy.
ECDSA (Elliptic Curve Digital Signature Algorithm) for signing and verification.
Ed25519 and X25519 are the modern, safer implementations based on Curve25519, designed by Daniel J. Bernstein to be constant-time and avoid common implementation pitfalls.
Production traps: - Curve selection matters: P-256 (secp256r1) is the widely interoperable default. P-384 gives slightly more headroom but is often overkill. Avoid curves like P-224 or secp160k1 outside very specialized contexts. - ECDSA requires a secure random nonce per signature. Reusing a nonce (even two signatures) reveals the private key. This has happened in real products: Sony's PlayStation 3 used a static k value, allowing attackers to extract the signing key. - ECDH with static keys: Without ephemeral keys, you lose forward secrecy. Use ECDHE (ephemeral ECDH) for every session. - Ed25519 is not yet widely accepted for all use cases (e.g., some FIPS modules exclude it). But it's the safest choice for new protocols due to its simple, constant-time design.
Performance: ECDH P-256 key agreement is about 10x faster than RSA-2048 key encapsulation. ECDSA verify is also faster than RSA verify. This matters for high-traffic APIs and IoT devices.
Post-Quantum Cryptography: Why Your RSA Keys Won't Survive the 2030s
Post-quantum cryptography (PQC) is the field of cryptographic algorithms designed to be secure against both classical and quantum computers. Shor's algorithm, when run on a sufficiently large fault-tolerant quantum computer, can break RSA and ECC by solving the underlying hard problems (integer factorization and discrete logarithm) in polynomial time. That's not a theoretical concern anymore NIST has been running a multi-year standardization process, and in 2024 they finalized the first set of standards.
The two main algorithms you need to know
ML-KEM (CRYSTALS-Kyber): Key encapsulation mechanism, replacing ECDH and RSA key exchange. Uses lattice-based cryptography. Provides 128-bit security classically and ~64-bit against quantum attacks.
ML-DSA (CRYSTALS-Dilithium): Digital signature algorithm, replacing ECDSA and RSA signatures. Slightly larger signatures than ECDSA but still practical.
FN-DSA (FALCON): Alternative signature scheme with smaller signatures but more complex implementation.
SLH-DSA (SPHINCS+): Stateless hash-based signatures, large but with high confidence in security.
When should you migrate? Don't wait for a quantum computer to exist. The 'harvest now, decrypt later' threat is real: attackers are already collecting encrypted traffic that will be decryptable once quantum computers become available. For data with long-term sensitivity (SSNs, medical records, state secrets), you should start migrating now using hybrid solutions: combine traditional (RSA/ECDH) with PQC (ML-KEM) so that even if one is broken, the other still holds.
Production reality in 2026: - Most cloud providers (AWS KMS, Google Cloud KMS) already support hybrid modes with ML-KEM. - TLS 1.3 has experimental hybrids (X25519+ML-KEM). - OpenSSH 9.x supports ML-KEM key exchange. - Java 21+ has limited test implementations; expect full support by Java 25 or 26. - Key sizes: ML-KEM-768 ciphertext ~1KB, ML-DSA-44 signature ~3KB (vs 64 bytes for Ed25519). That's a bandwidth cost.
Your action plan: - Enable hybrid key exchange in TLS wherever possible (e.g., OQS OpenSSL fork). - For long-term data encryption, use envelope encryption with hybrid wrapping (RSA + ML-KEM). - Monitor NIST and your cloud provider's announcements. By 2028, expect default PQC support. - Don't panic: classical crypto will coexist for another decade. But start testing now.
io/thecodeforge/dsa/HybridPqcEncryptor.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package io.thecodeforge.dsa;
// Note: This example uses placeholder Bouncy Castle PQC APIs (not final).// In production, use a library like OpenJDK's experimental PQC or OQS provider.import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.ByteBuffer;
import java.security.*;
import java.util.Base64;
/**
* Hybrid encryption: RSA-wrapped AES key + ML-KEM-wrapped AES key.
* Decryptor tries RSA first, then ML-KEM, to support gradual migration.
*/
publicclassHybridPqcEncryptor {\n\n static { Security.addProvider(newBouncyCastleProvider()); }
public static Stringencrypt(String plaintext, PublicKey rsaPublicKey, PublicKey mlkemPublicKey) throws Exception {\n // Generate ephemeral AES key\n KeyGenerator aesGen = KeyGenerator.getInstance(\"AES\");\n aesGen.init(256, new SecureRandom());\n SecretKey aesKey = aesGen.generateKey();\n\n // Wrap AES key with RSA (OAEP)\n Cipher rsaCipher = Cipher.getInstance(\"RSA/ECB/OAEPWithSHA-256AndMGF1Padding\");\n rsaCipher.init(Cipher.WRAP_MODE, rsaPublicKey);\n byte[] rsaWrapped = rsaCipher.wrap(aesKey);\n\n // Wrap AES key with ML-KEM (simplified - actual API varies)\n Cipher mlkemCipher = Cipher.getInstance(\"ML-KEM\");\n mlkemCipher.init(Cipher.WRAP_MODE, mlkemPublicKey);\n byte[] mlkemWrapped = mlkemCipher.wrap(aesKey);\n\n // Encrypt payload with AES-GCM\n byte[] nonce = new byte[12];\n new SecureRandom().nextBytes(nonce);\n Cipher aesGcm = Cipher.getInstance(\"AES/GCM/NoPadding\");\n aesGcm.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, nonce));\n byte[] ciphertext = aesGcm.doFinal(plaintext.getBytes());\n\n // Package: rsaWrappedLen, rsaWrapped, mlkemWrappedLen, mlkemWrapped, nonce, ciphertext\n ByteBuffer buf = ByteBuffer.allocate(4 + rsaWrapped.length + 4 + mlkemWrapped.length + 12 + ciphertext.length);\n buf.putInt(rsaWrapped.length);\n buf.put(rsaWrapped);\n buf.putInt(mlkemWrapped.length);\n buf.put(mlkemWrapped);\n buf.put(nonce);\n buf.put(ciphertext);\n return Base64.getEncoder().encodeToString(buf.array());\n }\n\n // Decryption mimics: try RSA unwrap, fallback to ML-KEM\n public static String decrypt(String encryptedBase64, PrivateKey rsaPrivateKey, PrivateKey mlkemPrivateKey) throws Exception {\n byte[] data = Base64.getDecoder().decode(encryptedBase64);\n ByteBuffer buf = ByteBuffer.wrap(data);\n\n int rsaLen = buf.getInt();\n byte[] rsaWrapped = new byte[rsaLen];\n buf.get(rsaWrapped);\n\n int mlkemLen = buf.getInt();\n byte[] mlkemWrapped = new byte[mlkemLen];\n buf.get(mlkemWrapped);\n\n byte[] nonce = new byte[12];\n buf.get(nonce);\n byte[] ciphertext = new byte[buf.remaining()];\n buf.get(ciphertext);\n\n SecretKey aesKey = null;\n // Try RSA first\n try {\n Cipher rsaCipher = Cipher.getInstance(\"RSA/ECB/OAEPWithSHA-256AndMGF1Padding\");\n rsaCipher.init(Cipher.UNWRAP_MODE, rsaPrivateKey);\n aesKey = (SecretKey) rsaCipher.unwrap(rsaWrapped, \"AES\", Cipher.SECRET_KEY);\n } catch (Exception e) {\n // RSA failed, fallback to ML-KEM\n Cipher mlkemCipher = Cipher.getInstance(\"ML-KEM\");\n mlkemCipher.init(Cipher.UNWRAP_MODE, mlkemPrivateKey);\n aesKey = (SecretKey) mlkemCipher.unwrap(mlkemWrapped, \"AES\", Cipher.SECRET_KEY);\n }\n\n Cipher aesGcm = Cipher.getInstance(\"AES/GCM/NoPadding\");\n aesGcm.init(Cipher.DECRYPT_MODE, aesKey, new GCMParameterSpec(128, nonce));\n byte[] plaintext = aesGcm.doFinal(ciphertext);\n return new String(plaintext);\n }\n}"
}
● Production incidentPOST-MORTEMseverity: high
The DES That Leaked Every Customer SSN
Symptom
Penetration test revealed that all customer SSNs, credit card numbers, and PII fields were encrypted with single DES in ECB mode. Ciphertexts of identical plaintexts were identical, revealing frequency patterns across records.
Assumption
The team assumed that because they were using a 'standard' cipher (DES), it was secure. Nobody reviewed the cipher string itself.
Root cause
Copy-pasted code with 'DES/ECB/PKCS5Padding' from a 2004 Stack Overflow answer. No code review checked the cipher algorithm. Compliance scanning only checked that encryption existed, not what kind.
Fix
Changed the cipher string to 'AES/GCM/NoPadding'. Rotated all encryption keys. Re-encrypted all existing data with AES-256-GCM via AWS KMS using envelope encryption.
Key lesson
Never trust encryption without auditing the exact cipher string and mode.
Treat 'DES/ECB' as a confirmed vulnerability, not a code smell.
Use automated static analysis to flag weak ciphers in code reviews.
Production debug guideSymptom → Action for common encryption failures5 entries
Symptom · 01
javax.crypto.AEADBadTagException thrown during decryption
→
Fix
Check nonce reuse: are nonces generated from a predictable source like an auto-increment ID? Check data corruption: is the ciphertext intact? Check key version mismatch: does the stored key version match the key in KMS?
Symptom · 02
Decryption produces garbage or partial plaintext
→
Fix
Verify key and IV/nonce used match the ones during encryption. Ensure base64 encoding/decoding is consistent. Check padding: GCM uses no padding, CBC requires correct padding scheme.
Symptom · 03
TLS handshake fails with 'no common cipher suites'
→
Fix
List supported cipher suites: openssl ciphers -v. Ensure both sides support TLS 1.3+ and AEAD ciphers. Check for deprecated ciphers like 3DES or RC4 being disabled on server.
Symptom · 04
Performance degradation after enabling encryption
→
Fix
Profile if AES-NI is available: /proc/cpuinfo should show aes flag. If not, switch to ChaCha20-Poly1305. Check for repeated RSA operations: each RSA-2048 encrypt is ~1000x slower than AES. Use hybrid encryption.
Symptom · 05
Key rotation breaks existing ciphertexts
→
Fix
Add key version identifier to ciphertext format. Use envelope encryption: store wrapped DEK with ciphertext. When master key rotates, re-wrap DEKs, not re-encrypt data.
★ Quick Debug Cheat Sheet: Encryption IssuesFor on-call engineers encountering encryption problems. Run these commands to diagnose.