Control vs property · Default is not a control
A strong cipher is not a secure implementation.
A Strong Cipher Is Not a Secure Implementation
Cryptographic primitives are rated by key size and best-known-attack complexity, while applied cryptography's documented failures, mode misuse, skipped verification, negotiable weak parameters and distinguishable decryption errors, all occurred with the primitive intact; where they touch, a deployment's security is set by the mode, the protocol state machine and the options left negotiable around the primitive, so naming the cipher is close to no evidence about the implementation.
“We encrypt with AES-256, so the data is secure.”
Fine.
In which mode?
Inside which protocol?
What else will that protocol agree to use?
What happens when decryption fails?
Usually the answers are blank looks, or a slide with a padlock on it.
A strong cipher is not a secure implementation.
FIPS 197 specifies AES as a block cipher.
It works on fixed 128-bit blocks, with a 128-, 192- or 256-bit key, through 10, 12 or 14 rounds.
That is what it does.
It turns one block into another block under a key, very well.
It doesn’t decide how a message longer than one block gets handled.
It doesn’t check whether ciphertext was tampered with.
It doesn’t choose what a server will agree to negotiate.
It doesn’t write your error messages, or decide how long they take to arrive.
Every one of those is a separate decision, made by somebody other than the cipher’s designers.
The documented failures happened in those decisions: the mode, the validation path, the options left open and the error handling.
The primitive came through every one of them untouched.
Nobody accepts “the steel is good grade” as an answer to “is the bridge safe?” They ask about the joints.
Put the word “AES” in the design doc and nobody asks about the joints.
1. “But AES-256 Has Never Been Broken.”
Correct.
That is precisely why it tells you nothing.
The best published attack on full-round AES is biclique cryptanalysis.
It needs 2^126.1 operations against AES-128’s nominal 2^128.
2^189.7 against AES-192’s 2^192.
2^254.4 against AES-256’s 2^256.
Subtract and you get 1.9, 2.3 and 1.6 bits respectively. Roughly two bits each.
A real reduction, and computationally irrelevant.
Those numbers describe the best attack currently published, and they move as cryptanalysis moves. For now they say the cipher is about as solid as anything in the field gets.
And a property every deployment shares cannot tell a good deployment from a bad one.
Adobe, 2013.
Roughly 150 million user passwords.
Encrypted, not hashed, which was already one wrong decision.
Then encrypted with block encryption in ECB mode, under a single key.
ECB encrypts each block on its own. No chaining. No nonce.
Identical plaintext blocks produce identical ciphertext blocks.
So researchers looking at the leaked database could group every account that shared a password, just by matching ciphertext.
The password hints sat next to them, unencrypted.
Read the hints across a group and the shared password tends to announce itself. One widely cited example: a ciphertext whose hints kept mentioning colours was almost certainly “blue”.
Nobody broke the cipher.
Nobody needed to.
The cipher did exactly what it is specified to do. The mode leaked the structure of the data, and the evidence was visible in the ciphertext without decrypting anything: repeated output blocks tracking repeated input blocks.
“Adobe’s encryption was weak” is the wrong summary.
The block cipher was fine. The mode was wrong for the data.
2. “But We Use TLS, and TLS Handles All of That.”
TLS handles a great deal of it, if every step actually runs.
TLS’s authentication guarantee depends on the client executing every check in certificate validation.
The signature.
The validity window.
The hostname.
The chain to a trusted root.
Skip one and the authenticated channel becomes an unauthenticated one that still shows the padlock.
That is not hypothetical. It is Apple’s goto fail, which the next section takes apart.
TLS is a protocol with a state machine, and every implementation of that state machine is code somebody wrote.
The protocol version matters too.
TLS 1.3, in RFC 8446, is a real structural improvement.
It defines only five cipher suites, and all of them are AEAD.
It drops RSA key transport and static Diffie-Hellman, neither of which is forward-secret.
It offers only ephemeral (EC)DHE and PSK-based resumption, so every full 1.3 handshake is forward-secret by construction.
The exception is 0-RTT early data on a resumed connection. An attacker who captured it can replay it to the same server.
That is a protocol doing the job properly: removing choices rather than trusting operators to make them.
But “we use TLS” doesn’t say which version you accept, which suites you still offer below it, whether your client library validates correctly, or where TLS ends.
It ends at the network boundary.
What your service does with the plaintext after termination is not TLS’s problem.
“We use TLS” names the protocol. It says nothing about your configuration or your code.
3. “But Our Crypto Library Passes Its Unit Tests.”
So did Apple’s.
CVE-2014-1266.
A duplicated goto fail; line in SSLVerifySignedServerKeyExchange, on affected iOS and OS X versions.
The second line ran unconditionally.
A signature-verification check was skipped every time, and the function returned success whether the signature was valid or not.
A man-in-the-middle could impersonate any TLS server to an affected client without holding any valid key at all.
The cipher suites were fine.
The signature check existed.
The code returned before it got there.
It shipped in production for months.
Why? Because every legitimate connection still succeeded.
Only a connection being actively intercepted behaved differently, and nothing in ordinary functional testing exercises that path.
Unit tests confirm that correct input gives correct output.
A validation bug doesn’t touch correct input.
It lives on the path where the input is wrong, and that path only runs when somebody is attacking you.
The code-review signature is specific: unreachable code after a conditional. A duplicated or misplaced control-flow statement that lets a validation function return before it reaches a check that should be mandatory.
The testing fix is equally specific.
Present a bad signature.
An expired certificate.
The wrong hostname.
An intercepted connection.
Then require the handshake to fail.
A validation test that has never watched validation refuse something is a test of the happy path, and attackers don’t use the happy path.
4. “But the Old Cipher Suites Are Only There for Legacy Clients.”
The legacy client is not the one who gets to choose.
The attacker is.
A downgrade attack forces two parties that both support a strong configuration to settle on a weaker one, by interfering with the negotiation rather than attacking the strong protocol.
FREAK (CVE-2015-0204) used leftover 1990s US export-grade cipher suites.
A client library that still supported export-strength 512-bit RSA could be tricked by a malicious or compromised server into using it, even when the client never intended to offer it.
A 512-bit RSA modulus was factorable within hours on commodity cloud compute.
Logjam is the Diffie-Hellman version.
A man-in-the-middle downgraded connections to export-grade 512-bit DH groups.
Many servers reused a small number of standard primes, so the expensive part of the attack could be precomputed once and reused cheaply against every connection on the same prime.
Researchers estimated that a nation-state-scale precomputation against one common 1024-bit prime would have broken a significant share of VPN and SSH traffic globally.
POODLE (CVE-2014-3566) needed the same door.
Browsers of the era retried failed handshakes over SSL 3.0. An attacker didn’t need a server that preferred SSL 3.0.
Only one that still accepted it as a fallback.
The root cause is identical across all three: parameters kept configured “for compatibility” for years after they stopped being safe to negotiate.
And the fix was identical too.
Not “prefer stronger suites.” Remove the weak option from what can be negotiated.
Preference is weaker than it sounds.
Negotiation is server-driven: the client lists what it supports, and the server picks.
RFC 9325 directs servers to enforce their own preference order, because a server that doesn’t can be steered to a weaker suite both sides happen to support.
The same document says implementations must not negotiate SSL 2.0, SSL 3.0, TLS 1.0 or TLS 1.1.
It disallows NULL encryption, RC4, and anything offering fewer than 112 bits of security.
It says static RSA key transport, static Diffie-Hellman, and CBC suites without encrypt_then_mac should not be used.
TLS 1.3 adds a downgrade tripwire: a server that sees a downgrade requested from 1.3 puts a fixed value in the last eight bytes of its random field, so a 1.3-capable client can detect a forced downgrade and refuse it.
That is how a protocol treats a legacy option: as attack surface, not as a courtesy to old clients.
5. “But the Decryption Error Messages Are Only for Debugging.”
They are for whoever reads them.
A padding oracle exists when a system’s response to a failed decryption, whether an error message, a timing difference or a connection reset, reveals whether the ciphertext’s padding was valid.
An attacker who can submit enough ciphertexts can learn the plaintext from nothing more than which failure comes back.
The cipher is untouched.
The failure handling leaks.
POODLE was this failure in SSL 3.0, which didn’t fully specify the contents of its padding bytes.
Lucky Thirteen (CVE-2013-0169) is the part that should worry anyone who thinks “we removed the detailed error string” settles it.
It affected TLS 1.1, TLS 1.2 and DTLS, after the padding-format ambiguity behind POODLE had been fixed.
Implementations that processed invalid CBC padding in non-constant time leaked enough timing signal, over many requests, to enable plaintext recovery.
Same message. Different arrival time. Same leak.
The evidence signature is distinguishable failure.
By content, or by timing.
The property you need is the opposite: every decryption failure looks the same, takes the same time, and tells an outside observer nothing.
Some primitives are designed to make that easier.
RFC 7748 states that Curve25519 and Curve448 were designed to lend themselves to constant-time implementation, resistant to timing and cache side channels.
Note the verb: lend themselves to.
The curve design makes constant time achievable.
Only the implementation makes it true.
6. “But ChaCha20 Is as Strong as AES, So Either Choice Is Fine.”
Either choice can be fine.
Between the right two things.
ChaCha20 is a stream cipher. It generates a keystream from a 256-bit key and a 96-bit nonce and XORs it against the plaintext.
ChaCha20 alone gives no integrity guarantee.
What gets deployed is ChaCha20-Poly1305, specified in RFC 8439 (June 2018, obsoleting RFC 7539), which pairs the cipher with the Poly1305 MAC to produce a 128-bit tag.
It is an authenticated-encryption construction from the start.
AES isn’t. AES needs a mode of operation, such as GCM, to become one.
So “ChaCha20 or AES?” compares a component with a component, and neither is what you deploy.
The meaningful comparison is ChaCha20-Poly1305 against AES-GCM: two complete constructions.
RFC 8439 positions the first as a software-friendly alternative to the second, for platforms without AES hardware acceleration.
That is a real engineering choice with a real answer.
It also comes with an operational limit that has nothing to do with “strength”.
A single key-nonce pair is bounded to roughly 256 GB of plaintext before the counter construction risks reuse.
That limit sits in your nonce management, not in the cipher.
Drop the Poly1305 half, reuse a nonce, or bolt AES into a mode with no authentication, and the “as strong as” sentence is still true.
Your deployment just stopped having the property the sentence was supposed to be about.
7. No, This Does Not Mean Primitive Choice Is Irrelevant.
It isn’t.
Key size still matters, and for asymmetric primitives it matters in a way the bare numbers hide.
NIST SP 800-57 Part 1 Revision 5 (May 2020) rates a 2048-bit RSA key at roughly 112-bit security.
A 3072-bit RSA key at roughly 128.
A 256-bit elliptic-curve key, such as P-256 or X25519, reaches that same 128-bit level at a fraction of the length.
A 1024-bit RSA key, still found in legacy deployments, rates only around 80-bit security, below NIST’s minimum for new use.
That table is a NIST recommendation, subject to revision, not a law of nature. It is still the table.
Logjam is the reminder that size matters even when the math is sound: a 1024-bit prime shared across many servers was a target worth a nation-state’s precomputation.
So choose an approved primitive at an adequate size.
That is necessary.
It is the first decision, not the one that decides the outcome.
Once an approved primitive is chosen, the failures that actually happen are in mode, protocol and configuration.
The cipher is the part of the system least likely to be the problem.
It is also usually the only part anyone names.
8. What You Should Actually Do.
Specify complete constructions, never a bare cipher. Write “AES-GCM” or “ChaCha20-Poly1305” in the requirement. “AES-256” is a component, and a component in ECB mode satisfies it.
Remove legacy options instead of preferring newer ones. Export-grade suites, SSL 3.0, TLS 1.0 and 1.1 come out of what servers and clients can negotiate. RFC 9325 requires TLS 1.2, prefers TLS 1.3 and refuses everything older. Enforce server-side preference order for what remains.
Make decryption failures uniform. Same response, same content, same timing, whatever went wrong. Test for timing differences, not only message differences.
Review validation paths for early returns. Look for unreachable code after a conditional in signature and certificate checks. Then test those paths against intercepted connections, bad signatures, expired certificates and wrong hostnames, and require failure.
Prefer TLS 1.3. Key exchange is ephemeral by construction and the suite list is AEAD-only. Treat 0-RTT as a replay-exposed exception you enable deliberately, not a default.
Record more than the algorithm in the cryptographic inventory. Primitive, mode or construction, and library version. The next goto fail will live in a specific library version, and you will want to know where it is deployed.
9. The Model Is Not Complicated.
A primitive is rated by one thing: how hard the best known attack is.
For AES, that rating is excellent and has been for a long time.
A deployment is rated by everything around the primitive.
The mode, which decides whether structure leaks.
The validation path, which decides whether authentication actually happens.
The negotiable set, which decides what an attacker can push you down to.
The failure handling, which decides what a wrong guess teaches the attacker.
Adobe, goto fail, FREAK, Logjam, POODLE and Lucky Thirteen all failed in that second list.
None of them required breaking the cipher.
So when someone says “we encrypt with AES-256”, the correct response isn’t “great”.
It is: in which mode, validated how, negotiable down to what, failing how?
If nobody can answer, you haven’t described a secure implementation.
You have named a very good block cipher and assumed the rest.
Objections
- The best published attack on full-round AES, biclique cryptanalysis, needs 2^126.1 operations against AES-128's nominal 2^128, a reduction of roughly two bits that is computationally irrelevant.
- ECB mode encrypts identical plaintext blocks to identical ciphertext; Adobe encrypted roughly 150 million passwords with ECB under one key, so researchers could group accounts sharing a password and read the shared plaintext from password hints, with the block cipher itself unbroken.
- Apple's goto fail bug (CVE-2014-1266) skipped a signature-verification check unconditionally, letting a man-in-the-middle impersonate any TLS server; it shipped for months because every legitimate connection still succeeded.
- FREAK and Logjam downgraded connections to export-grade 512-bit RSA and Diffie-Hellman left configured for compatibility, and the fix was removing those options from what could be negotiated; TLS 1.3 drops RSA key transport and static Diffie-Hellman, offering only ephemeral (EC)DHE and PSK-based resumption, so its handshakes are forward-secret by construction, 0-RTT early data aside.
- ChaCha20-Poly1305 is specified as an authenticated-encryption construction from the start, whereas AES needs a mode of operation to become one; ChaCha20 alone gives no integrity guarantee.
What this does not say
What this does not mean is that primitive choice is irrelevant; a 1024-bit RSA key rates around 80-bit security, below NIST's minimum for new use, and key size still matters. It means that once an approved primitive is chosen, the failures that actually happen are in mode, protocol and configuration.
Raised in review
- Has AES been broken?
- No. The best published attack on full-round AES is biclique cryptanalysis, which needs 2^126.1 operations against AES-128's nominal 2^128, 2^189.7 against AES-192's 2^192 and 2^254.4 against AES-256's 2^256. That is a reduction of roughly two bits in each case and nowhere near practical. The figures describe the best attack currently published, so they are dated evidence rather than permanent constants.
- What is wrong with ECB mode?
- ECB encrypts each block independently, with no chaining and no nonce, so identical plaintext blocks always produce identical ciphertext blocks. The cipher works exactly as specified, but the output preserves the structure of the input. Anyone holding the ciphertext can see which blocks repeat without decrypting anything. That makes it the wrong mode for any data with repeated structure, which covers almost all real data.
- What happened with Adobe's password encryption?
- Adobe encrypted, rather than hashed, roughly 150 million user passwords with ECB-mode block encryption under a single key. Because ECB preserves equality, researchers examining the leaked database could group every account sharing a password by its identical ciphertext. The password hints were stored unencrypted, so the hints across a group often gave the shared password away. The block cipher was never attacked; the mode and the storage decision failed.
- What was Apple's goto fail bug?
- CVE-2014-1266 was a duplicated goto fail line in SSLVerifySignedServerKeyExchange on affected iOS and OS X versions. It caused a signature-verification check to be skipped unconditionally, so the function reported success whether or not the signature was valid. A man-in-the-middle could impersonate any TLS server to an affected client without holding any valid key. Legitimate connections still worked, which is why it shipped for months.
- What did FREAK and Logjam have in common?
- Both forced connections down to export-grade 512-bit parameters that were still configured for compatibility. FREAK (CVE-2015-0204) pushed a client into export-strength RSA whose modulus could be factored within hours on commodity cloud compute. Logjam did the same with 512-bit Diffie-Hellman groups and exploited servers reusing a few standard primes. In both cases the fix was removing the weak option from what could be negotiated at all.
- Is preferring strong cipher suites enough if weak ones are still enabled?
- No. Preference only matters while the negotiation behaves as intended, and downgrade attacks interfere with the negotiation itself. Negotiation is server-driven, and RFC 9325 directs servers to enforce their own suite order because a server that does not can be steered to a weaker mutually supported suite. RFC 9325 also says implementations must not negotiate SSL 2.0, SSL 3.0, TLS 1.0 or TLS 1.1. An option that is present can be reached.
- Why does TLS 1.3 remove options instead of adding safer defaults?
- Removing an option means no operator has to remember to disable it. RFC 8446 defines only five cipher suites, all AEAD, and drops RSA key transport and static Diffie-Hellman, neither of which is forward-secret. TLS 1.3 offers only ephemeral (EC)DHE and PSK-based resumption, so every full handshake is forward-secret by construction. The exception to watch is 0-RTT early data, which an attacker who captured it can replay to the same server.
- What is the warning sign of a padding oracle?
- Decryption failures that an outside observer can tell apart. If a system answers bad padding with a different error message, a different response time or a connection reset, an attacker who can submit many ciphertexts can learn about the plaintext from those responses. POODLE (CVE-2014-3566) was that failure in SSL 3.0. Lucky Thirteen (CVE-2013-0169) did it through timing alone in TLS 1.1, TLS 1.2 and DTLS, after the message-content problem had been fixed.
- Is ChaCha20 a replacement for AES?
- ChaCha20-Poly1305 is an alternative to AES-GCM, not to AES on its own. RFC 8439 specifies it as an authenticated-encryption construction from the start: the ChaCha20 stream cipher with a 256-bit key and 96-bit nonce, paired with the Poly1305 MAC producing a 128-bit tag. The RFC positions it for platforms without AES hardware acceleration. ChaCha20 alone gives no integrity guarantee, so the comparison only means something between complete constructions.
- Does key size still matter?
- Yes. NIST SP 800-57 Part 1 Revision 5 rates a 2048-bit RSA key at roughly 112-bit security and a 3072-bit key at roughly 128-bit, while a 256-bit elliptic-curve key reaches about 128-bit. A 1024-bit RSA key, still found in legacy deployments, rates only around 80-bit, below NIST's minimum for new use. Choosing an approved primitive at an adequate size is necessary. It is the first decision, not the last one.
- Why do unit tests miss cryptographic validation bugs?
- Unit tests usually check that correct inputs produce correct results, and a validation bug does not affect correct inputs. With goto fail, every legitimate connection still succeeded; only a connection being actively intercepted behaved differently, and ordinary functional testing never exercised that path. Validation code needs negative tests that present a bad signature, an expired certificate or a wrong hostname and require the connection to fail.
- What should a cryptographic inventory record besides the algorithm?
- Record the primitive, the mode or complete construction it runs in, and the library and version that implements it. AES-256 in ECB and AES-256 in GCM share a primitive and have almost nothing else in common. The library version matters because implementation bugs such as goto fail or Lucky Thirteen's timing leak live in a specific codebase, and fixing them means knowing where that codebase is deployed.