Lucky Thirteen Attack Explained

Lucky Thirteen Attack Explained

Written by

in

The lucky thirteen attack showed that a padding oracle does not need an error message to exist. It can live entirely in time. When an old TLS connection used a CBC ciphersuite with the MAC then encrypt construction, the server took a few nanoseconds longer to process some forged records than others, and that tiny timing difference depended on the plaintext the attacker was trying to recover. Measured carefully across many connections, that difference leaked the plaintext one byte at a time. This post explains where the timing comes from, why the number thirteen matters, and why constant time checks and authenticated encryption are the fix.

What the lucky thirteen attack exploits

Older TLS records protected data with a cipher in CBC mode wrapped around a message authentication code, arranged as MAC then encrypt. The sender computed a MAC over the plaintext, appended it, padded the result to a block boundary, and then encrypted the whole thing. The receiver ran that in reverse: decrypt, strip the padding, then check the MAC.

That order is the root problem, and it is the same ordering flaw behind the classic padding oracle attack. The receiver makes decisions about decrypted bytes before it has confirmed those bytes are authentic. The attacker supplies a forged record, the receiver decrypts it into attacker influenced plaintext, and then the work the receiver does next depends on what that plaintext looks like. If the attacker can observe that work, even just how long it takes, they learn something about the plaintext.

Where the timing difference comes from

After decryption, the receiver reads the last byte of the plaintext to learn how many padding bytes to remove, then verifies the MAC over what is left. The MAC here is HMAC, built on a hash that processes data in fixed size blocks. The time HMAC takes depends on how many blocks it has to compress. That is the hinge.

Consider two forged records. In one, the decrypted plaintext happens to end in a byte that claims a long run of padding. The receiver strips a lot, leaving little data for the MAC, which then runs over fewer hash blocks and finishes sooner. In the other, the plaintext ends in a byte claiming a single byte of padding. The receiver strips almost nothing, feeds more data into the MAC, and it runs one extra hash compression, finishing a hair later.

record = [ ciphertext that decrypts to chosen plaintext ]
receiver:
    plaintext = decrypt(record)
    n = plaintext[last]          # claimed padding length
    data = plaintext[:-n-1]      # bytes left for the MAC
    check HMAC over data         # time depends on block count of data

The attacker never sees the plaintext or the padding length. They only time the response. But because the amount of data fed to HMAC changes with the padding the attacker forced, the response time carries a signal about whether the decrypted bytes formed a particular padding pattern. That is a padding oracle. It is just measured in time instead of read from an error.

The server returned the same error either way and revealed nothing on purpose. It still leaked the secret, because how long it took to say no depended on the very bytes it was trying to protect.

Why it is called lucky thirteen

The name comes from the arithmetic of the record. Before HMAC runs, the data it hashes includes a header of sequence number and record metadata that adds up to 13 bytes, sitting in front of the actual payload. That 13 byte prefix shifts where the hash block boundaries fall. It decides the exact point at which feeding one more byte of payload tips HMAC into computing an extra compression block, which is the boundary the whole timing signal rides on. The researchers who found it, Nadhem AlFardan and Kenneth Paterson, named the attack after the thirteen bytes that set up that lucky boundary. It was disclosed in 2013 and tracked as CVE-2013-0169.

Turning a timing blip into recovered plaintext

A single timing measurement is noisy. Network jitter, scheduling, and caching all swamp a few nanoseconds. The attack wins by repetition. The attacker sends the same forged record shape thousands of times and averages the timings. With enough samples, the difference between a record that produced valid padding and one that did not becomes measurable.

From there the structure matches any padding oracle. The attacker targets one ciphertext byte at a time. They tweak the preceding ciphertext block so the byte under attack decrypts to a guessed value, submit the record, and time the response. When the timing says the padding check passed, they have learned the intermediate value for that byte, and from it the plaintext byte. They walk across the block, byte by byte, until the record is recovered. Picture an invented app, Acme Mail, whose session token rides in a cookie sent on every request over one of these old CBC ciphersuites. A man in the middle who can replay the record and time the server can, over many thousands of measurements, reconstruct that token without ever holding the key.

Why constant time is the real fix in the code

The deep cause is that a security check took a different amount of time depending on secret data. Any check that branches or loops based on a secret leaks that secret through timing. This is exactly why sensitive comparisons must run in constant time, a point we cover in constant time comparison. A MAC verify that bails out early, a padding strip whose loop length depends on the padding value, a memory compare that stops at the first mismatch, all of them turn a secret into a stopwatch reading.

Patching lucky thirteen in CBC code meant rewriting the decrypt path so it always does the same amount of work regardless of the padding. The implementation computes the MAC over a fixed number of blocks no matter what the padding byte claims, adding dummy compressions to flatten the timing. It is fiddly, easy to get subtly wrong, and a reminder that constant time behavior has to be designed in, not hoped for.

The fix that removes the problem entirely

The durable answer is to stop using MAC then encrypt CBC ciphersuites at all and move to authenticated encryption with associated data, or AEAD. Modes like AES-GCM and ChaCha20-Poly1305 authenticate and encrypt in one primitive with no separate padding step to strip and no separate MAC to misorder. There is no padding length for a timing signal to depend on, so the oracle has nothing to measure.

  • Prefer AEAD ciphersuites and disable the legacy CBC MAC then encrypt suites where you can.
  • Enforce a modern minimum protocol version so a connection cannot quietly select an old, vulnerable suite.
  • If you must keep CBC, use only a verified constant time implementation, not hand rolled decrypt code.

Lucky thirteen belongs to a wider family of timing and oracle bugs. For the full set, start at our hub on cryptographic oracle attacks, and for more entry level write ups see the vulnerability basics category.

The assumption that breaks

The assumption lucky thirteen destroyed is that returning the same error is the same as revealing nothing. The server was careful. It gave one error for any failure and leaked no message. But it forgot that time is also a channel, and the time it spent depended on the secret. That gap, between the information a system thinks it is hiding and the information an attacker can actually measure, is the kind of flaw an autonomous researcher that tests assumptions is built to probe, by asking whether a comparison is actually constant time rather than just quiet. Learn more about that approach on our about page.

Frequently asked questions

What is the lucky thirteen attack?

It is a timing based padding oracle attack against older TLS connections that use a CBC ciphersuite with the MAC then encrypt construction. The time the server takes to reject a forged record depends on the padding in the decrypted plaintext, and by measuring that time over many requests an attacker recovers the plaintext one byte at a time. It was disclosed in 2013 as CVE-2013-0169.

Why is it called lucky thirteen?

The data the MAC hashes includes 13 bytes of header in front of the payload. Those 13 bytes shift where the hash block boundaries fall, which decides the exact point at which one more byte of payload forces an extra hash compression. That boundary is what makes the timing signal measurable, so the researchers named the attack after it.

Does an error message matter, or is timing enough?

Timing alone is enough. TLS implementations had already been changed so bad padding and bad MAC returned the same error, which closed the obvious leak. Lucky thirteen showed that how long the server took still depended on the secret bytes, so a side channel can be pure time with no distinct error at all.

How do you fix the lucky thirteen attack?

Move to authenticated encryption ciphersuites such as AES-GCM or ChaCha20-Poly1305, which have no separate padding step for a timing signal to depend on, and enforce a modern minimum protocol version. If a CBC suite must stay, use only a verified constant time implementation that does the same amount of work regardless of the padding.


Put an autonomous researcher on your own systems

UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.