Author: UnboundCompute

  • Terrapin Attack Explained

    Terrapin Attack Explained

    The terrapin attack is a prefix truncation attack on the SSH secure channel. An attacker sitting on the network does not break SSH encryption. Instead, they trim a few messages off the start of the encrypted conversation so carefully that neither end notices anything is missing. Because SSH numbers its messages but the two ends never compare those numbers after the handshake, deleting a message early and inserting a matching one earlier keeps the counts lined up, and the channel starts in a state the attacker shaped. This post explains how SSH sequences its packets, why truncation goes undetected, what the attacker gains, and why the strict handshake countermeasure closes it. It was disclosed in 2023 and tracked as CVE-2023-48795.

    What the terrapin attack changes

    SSH protects a session in two stages. First the two ends run a key exchange, agreeing on keys and on the algorithms they will use. Then every packet after that is wrapped in the binary packet protocol, encrypted and authenticated so it cannot be read or altered in flight. The integrity check uses a running sequence number: each packet carries an implicit counter, and the authentication covers that counter, so a packet cannot be reordered, replayed, or dropped without the check failing.

    That is the promise. The terrapin attack shows the promise has a gap at the seam between the handshake and the encrypted channel. The sequence number counter starts ticking during the handshake, while some messages are still sent in the clear. An attacker who adds a harmless message in the clear early, then removes a real encrypted message later, can keep the final counter exactly where the honest run would have left it. The integrity check sees consistent numbering and raises no alarm, even though the transcript was edited.

    How prefix truncation stays invisible

    The trick is an accounting one. During the handshake, SSH allows certain optional or ignorable messages. The attacker injects one of these in the clear before encryption starts, which bumps the sequence counter by one on that side. Then, once the channel is encrypted, the attacker deletes the first real encrypted message. That deletion would normally shift every later sequence number by one and break the very next integrity check. But the earlier injected message already shifted the count the other way, so the two cancel. The counter the two ends expect still matches. The deleted message is simply gone, and nothing downstream reveals that it ever existed.

    honest run:   ... [msgA] [msgB=N] [msgC=N+1] ...
    attacker run: [injected=clear] ... [msgB deleted] [msgC=N] ...
                  # counter at msgC lines up, integrity check passes

    So the attacker cannot read the encrypted traffic, but they can chop a known message off the front of the secure channel. That is the prefix truncation. The first thing the secure channel was supposed to say is silently removed, and the session continues as if it was never sent.

    Nothing was decrypted and no key leaked. The attacker only deleted the opening of the conversation, and the integrity check, counting by a number that still added up, waved it through.

    Why a deleted opening message matters

    Removing the first message of a secure channel sounds minor until you see what that message often is. SSH sends an extension negotiation message, SSH_MSG_EXT_INFO, right at the start of the encrypted channel. It tells the other end which extra features and, in some cases, which signature algorithms are in play. If an attacker deletes that message, they strip the negotiated extensions without either end knowing.

    In practice this lets an attacker turn off a security feature that was agreed during the handshake, for example a countermeasure that protects against a weakness in certain signature methods. The endpoints believe the feature is active because they negotiated it. It is not active, because the message announcing it never arrived. This is a downgrade achieved by deletion rather than by rewriting the negotiation, which makes it a close relative of the cipher downgrade attack, where a man in the middle steers two ends onto weaker crypto. There the attacker edits the offered options. Here the attacker deletes the message that would have switched a protection on.

    Which connections were affected

    The attack does not work on every SSH connection. It depends on specific modes where the sequence number handling at the handshake boundary lines up with what the attacker needs, most notably the ChaCha20-Poly1305 mode and certain encrypt then MAC CBC modes. Connections using other authenticated modes were not exposed in the same way. That is why the practical advice was not panic but patch, since the fix is clean and the exposure is bounded to particular algorithm choices.

    The fix: the strict handshake countermeasure

    The root cause is that the sequence number counter spanned both the unauthenticated handshake and the authenticated channel, so a message injected in the clear could offset a deletion made later. The countermeasure, called strict key exchange or the strict handshake, fixes exactly that.

    • Reset the sequence number after key exchange. With the strict handshake, the packet counter is set back to zero once keys are established. A message the attacker injected in the clear before that point no longer offsets anything afterward, because the count restarts. A deletion in the encrypted channel now shifts the numbering and the next integrity check fails, which is what should have happened all along.
    • Refuse unexpected messages during the handshake. The strict handshake also rejects the optional messages an attacker would inject during key exchange, so the offset move is not even available.
    • Patch both ends. The countermeasure only applies when both the client and the server support it, so updating only one side is not enough. Update both, and confirm the strict handshake is actually in use.

    The broader lesson is familiar. An integrity check is only as strong as the exact thing it covers, and a counter that is trusted across a trust boundary can be gamed on one side of it. For more attacks in this family, start at our hub on cryptographic oracle attacks, and for more write ups from the ground up see the vulnerability basics category.

    The assumption that breaks

    The assumption the terrapin attack breaks is that an authenticated channel authenticates its own beginning. The messages inside were protected, but the boundary where the counter started was not, and an attacker worked the seam. The secure channel looked intact to both ends while its first words had been cut away.

    Asking whether a handshake step is actually authenticated from its very first byte, rather than assuming the channel protects everything once encryption turns on, is exactly the kind of question an autonomous researcher that tests assumptions is built to ask. Learn more about that approach on our about page.

    Frequently asked questions

    What is the terrapin attack?

    It is a prefix truncation attack on SSH, disclosed in 2023 as CVE-2023-48795. An attacker on the network deletes the first messages of the encrypted SSH channel without being detected, by injecting an ignorable message in the clear during the handshake so the sequence number counter still lines up after the deletion. The attacker cannot read the traffic but can cut the opening of the secure conversation.

    Why does the integrity check not catch the truncation?

    SSH numbers its packets and the authentication covers that running count, so a dropped packet normally breaks the next check. The problem is that the counter starts during the handshake while some messages are still in the clear. An attacker injects one clear message early and deletes one encrypted message later, and the two offsets cancel, so the counter both ends expect still matches and no alarm fires.

    What can an attacker gain from the terrapin attack?

    Deleting the first message of the secure channel can strip the extension negotiation message that SSH sends right after key exchange. That lets an attacker silently turn off a security feature both ends thought they had agreed on, such as a countermeasure protecting certain signature methods. It is a downgrade achieved by deleting a message rather than by rewriting the negotiation.

    How do you fix the terrapin attack?

    Apply the strict key exchange countermeasure, often called the strict handshake, which resets the packet sequence number to zero after key exchange and refuses unexpected messages during the handshake. With the counter reset, an injected clear message no longer offsets a later deletion, so truncation breaks the next integrity check. Both the client and the server must support it, so patch both ends.


    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.

  • Chosen Prefix Collision Attack Explained

    Chosen Prefix Collision Attack Explained

    A chosen prefix collision attack turns a weak hash function into a forgery tool. The attacker starts with two different inputs they want to pass off as one, a real certificate and a rogue one, or a clean contract and a swapped version, and then computes trailing bytes that drive both inputs to the exact same hash value. A signature made over one input is then a valid signature over the other. This post explains how a collision differs from a preimage, why the chosen prefix variety is the dangerous one, how broken hashes like MD5 and SHA-1 make it practical, and why the only real fix is to stop using those hashes at all.

    What a chosen prefix collision attack is

    A hash function takes any input and returns a fixed size digest. A collision is two different inputs that produce the same digest. Collisions always exist in theory, because there are more possible inputs than outputs. The whole job of a secure hash is to make finding one cost more compute than anyone has.

    There are two flavors of collision, and the gap between them is the whole story.

    • An identical prefix collision forces the attacker to start both inputs with the same fixed bytes. The two files differ only in some carefully crafted gibberish that the attacker controls from the first byte. Useful, but awkward, because both documents share a common opening the attacker did not get to choose freely.
    • A chosen prefix collision lets the attacker pick two completely different, meaningful prefixes first, say the real data for Alice and the real data for Mallory, and then compute suffixes that make the two whole messages collide. The meaningful part is chosen. Only the trailing filler is crafted.

    That freedom is what makes the chosen prefix case usable against real formats. A certificate, a document, or a signed blob has structure the attacker does not control. A chosen prefix collision works around that structure instead of fighting it.

    Why the Merkle Damgard design makes suffixes stick

    Most classic hashes, including MD5 and SHA-1, are built the same way. They process the message in fixed size blocks, carrying a running internal state from one block to the next. Each block mixes into the state, and the final state is the digest.

    state = IV
    for block in message:
        state = compress(state, block)
    digest = state

    One property of this design hands the attacker a gift. If two different messages reach the same internal state at some point, then appending the same bytes to both keeps them equal forever after. Collide once, and anything you glue on after the collision keeps the collision intact.

    So the attack has a clear shape. Take prefix P1 and prefix P2. They push the hash into two different internal states. The attacker then computes a pair of collision blocks, call them C1 and C2, that nudge those two different states back into one shared state. This is the expensive part, built from what researchers call near collision blocks and differential paths through the compression function. Once the states match, the attacker appends one identical suffix S to both, and the two full messages hash to the same value.

    hash( P1 || C1 || S ) == hash( P2 || C2 || S )

    Neither message looks like noise from the outside. The prefixes are real. The suffix is real and shared. Only the short collision blocks in the middle are crafted, and formats are full of places to hide a few opaque bytes, a comment field, an image thumbnail, an unused extension.

    The signer signs what they read. The attacker makes a second document the signer never saw carry the same signature. The cryptography that was supposed to bind a name to bytes now binds it to two sets of bytes at once.

    From a collision to a forged certificate

    Picture a certificate authority for an invented service, Acme Trust. It signs certificates by hashing the certificate body and signing the hash. If the attacker can produce two certificate bodies that hash to the same value, one harmless and one granting power the attacker should never have, then a signature over the harmless one is also valid over the dangerous one.

    The attacker submits the benign certificate for signing. The authority checks it, hashes it, signs the hash, and returns the signature. The attacker lifts that signature and staples it onto the rogue certificate, which hashes to the same digest. Now they hold a signed certificate the authority never agreed to. The same move works on any signed object. Sign the version you showed me, and I attach your signature to the version I hid.

    This is not hypothetical history. A chosen prefix collision against MD5 was used to build a rogue certificate authority in 2008, and the Flame malware in 2012 forged a code signing certificate by the same family of technique. SHA-1 fell to a chosen prefix collision in 2020, years after it was already being retired. The pattern repeats: a hash is declared risky, stays in production because migration is annoying, and then a working chosen prefix collision arrives and forces the issue.

    How this sits next to other hash attacks

    A collision is not the only way to abuse a hash, and it helps to keep the classes apart. A collision finds two inputs with one digest. A length extension attack, covered in our post on the hash length extension attack, instead takes a known digest and extends the message without knowing the secret, also a consequence of the Merkle Damgard design. Both are about the same family of hashes leaking more structure than their users assumed. For the wider set of tricks that turn a cryptographic assumption into an opening, see our hub on cryptographic oracle attacks.

    The fix: retire the broken hashes

    There is no clever configuration that makes MD5 or SHA-1 safe for signatures. The collision resistance they were chosen for is gone. The fix is to move to a hash with no known collision weakness and to refuse the old ones outright.

    • Use SHA-256 or stronger for any signature, certificate, or integrity check that an attacker could benefit from forging. SHA-256, SHA-384, SHA-512, and the SHA-3 family have no practical collision attack.
    • Reject deprecated hashes at the boundary. Do not merely prefer SHA-256, actively refuse MD5 and SHA-1 signatures so a downgrade cannot quietly select them. A verifier that still accepts a SHA-1 signature is still exposed.
    • Check the whole chain. A certificate chain is only as strong as its weakest hash. One SHA-1 link anywhere in the path reopens the door.
    • Do not confuse uses. MD5 as a non security checksum for detecting accidental corruption is a different question from MD5 under an adversary. If an attacker gains by a match, you need a hash that resists chosen prefix collisions.

    If you are new to how cryptographic weaknesses become real world bugs, our vulnerability basics category walks through more of these from the ground up.

    The assumption that breaks

    The quiet assumption behind every signature is that a digest names exactly one document. A chosen prefix collision breaks that assumption while leaving the signature math untouched. Nothing is forged in the cryptographic sense. The signer really did sign that digest. The problem is that the digest was never unique, and a system that trusted it to be unique now trusts two documents equally.

    That gap, between what a system assumes a hash guarantees and what a broken hash actually guarantees, is the kind of thing an autonomous researcher that tests assumptions is built to notice, by asking whether a signature step really binds what everyone believes it binds. Learn more about that approach on our about page.

    Frequently asked questions

    What is the difference between a collision and a chosen prefix collision?

    A plain collision is any two different inputs that hash to the same value, but the attacker may not control what those inputs look like. A chosen prefix collision lets the attacker pick two meaningful, different prefixes first, such as a real certificate and a rogue one, then compute trailing bytes that make both whole messages hash to the same digest. That control over the meaningful part is what makes it usable against real file and certificate formats.

    Why are MD5 and SHA1 vulnerable when SHA-256 is not?

    MD5 and SHA-1 have known weaknesses in their compression functions that let researchers build collision blocks far faster than brute force. A practical chosen prefix collision against MD5 was shown years ago, and SHA-1 fell to one in 2020. SHA-256 and stronger hashes have no such practical collision attack today, which is why signatures should move to them.

    How does a hash collision let someone forge a certificate?

    A certificate is signed by hashing its body and signing that hash. If an attacker makes two certificate bodies that hash to the same value, one harmless and one granting power they should not have, a signature over the harmless body is also valid over the dangerous one. The attacker gets the benign version signed, then attaches that signature to the rogue version.

    Is MD5 safe to use for anything?

    MD5 is only acceptable where no attacker benefits from a match, such as a checksum that detects accidental file corruption. Any use where an adversary gains from two inputs sharing a digest, such as signatures, integrity checks, or certificates, needs a hash that resists chosen prefix collisions like SHA-256 or stronger.


    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.

  • Cipher Downgrade Attack Explained

    Cipher Downgrade Attack Explained

    A cipher downgrade attack does not break strong encryption. It talks the two ends out of using it. A man in the middle sits between a client and a server while they negotiate how to protect the connection, and quietly steers that negotiation toward the weakest option both sides will still accept, an export grade key, an obsolete cipher, sometimes no encryption at all. Both ends think they agreed on the best they could. They actually agreed on something an attacker can break. This post explains how negotiation works, how it gets subverted, the FREAK and Logjam class of real world examples, and how to shut the door.

    How a cipher downgrade attack works

    Before a TLS connection carries any data, the two ends run a handshake to agree on a ciphersuite. The client sends a list of the suites it supports, in preference order. The server picks one it also supports and replies with the choice. The idea is that each side advertises what it can do and they settle on the strongest common option.

    A cipher downgrade attack attacks that settling step. The attacker is positioned on the network path, so they can read and rewrite the handshake messages as they pass. There are two broad moves.

    • Strip the strong options. The attacker edits the client hello as it goes by, deleting the strong ciphersuites from the list so the server only sees weak ones. The server picks the best of what is left, which is now weak, and believes that is all the client offered.
    • Force a weak suite directly. In some flawed protocols the attacker can inject a message that makes the server agree to an export grade or null cipher even though neither side would have chosen it on its own.

    Either way the connection ends up on crypto the attacker can defeat, while the handshake looks like a normal agreement to both the client and the server.

    Export grade keys: weakness that was built in on purpose

    To see why a weak option was even on the menu, you have to go back to 1990s export rules. For years, software shipped outside the United States was legally limited to short keys, so many products included export grade ciphersuites with 512 bit RSA or 512 bit Diffie Hellman, deliberately weak so they could be broken by the authorities of the day. The rules were later relaxed, but the weak suites stayed in the code and in server configs for decades, switched off by default but still present.

    That leftover is the fuel for the attack. A 512 bit RSA key can be factored with modest resources today. A 512 bit Diffie Hellman group can be broken after a one time precomputation against the common shared parameters, and then each individual connection cracked quickly. So if an attacker can force a connection down to an export suite, they can recover the session key and read or tamper with everything that follows.

    The math did not fail and the strong ciphers held. The attacker simply convinced both ends to use the weak ones that should have been deleted years earlier.

    The FREAK and Logjam class

    Two disclosed attacks made this concrete. FREAK, short for Factoring RSA Export Keys and tracked as CVE-2015-0204, worked because many servers still accepted export grade RSA and many clients had a bug that let a man in the middle swap in an export RSA key exchange the client never asked for. The attacker forced the weak key exchange, factored the 512 bit key, and took over the session.

    Logjam was the Diffie Hellman cousin. A man in the middle downgraded the connection to export grade Diffie Hellman, which used 512 bit groups. Because a small set of those groups was shared across huge numbers of servers, an attacker could do the heavy precomputation once and then break individual handshakes cheaply. Different primitive, same shape: a weak option left enabled, plus a negotiation an attacker could steer, equals a broken connection.

    The older POODLE attack had the same downgrade ingredient, forcing a connection back to obsolete SSLv3 so a padding weakness became usable. In every case the downgrade was the enabling step, not the final payload.

    Why the negotiation was steerable at all

    The deeper issue is that early handshake messages were not always protected against tampering. If the list of offered ciphersuites is sent in the clear and nothing later verifies that both ends saw the same list, an attacker can edit it and neither side notices. The handshake finishes, a shared key is derived, and the tampering leaves no mark the endpoints check.

    This is closely related to how much a passive observer can already tell from a handshake. The ordered list of ciphersuites and extensions a client offers is distinctive enough to identify the client software, which we cover in how TLS fingerprinting works. The same handshake fields that reveal a client’s identity are the fields a downgrade attacker rewrites.

    The fix: remove the weak options and sign the negotiation

    A downgrade attack needs two things to work: a weak option that is still acceptable, and a negotiation an attacker can influence without detection. Remove either and the attack dies. In practice, close both.

    • Remove weak ciphersuites entirely. Disable export grade key exchange, null encryption, and obsolete ciphers on both clients and servers. An option that is not offered cannot be forced. This is the single most effective step.
    • Enforce a modern minimum protocol version. Refuse to negotiate below a current TLS version so a rollback to an old, weak protocol simply fails rather than succeeding quietly.
    • Authenticate the negotiation. Modern TLS binds the handshake so that at the end both ends verify a MAC computed over every handshake message they exchanged. If an attacker edited the ciphersuite list, the two transcripts differ, the verification fails, and the connection aborts. The negotiation is effectively signed, so tampering is caught.
    • Do not rely on fallback retries. Automatic retries at a lower version, meant to help with buggy servers, are exactly what an attacker abuses to force a downgrade. Remove insecure fallback.

    Weak ciphers bite in other ways too. Even without a forced downgrade, a short block cipher can leak data over a long lived connection, as covered in the sweet32 birthday attack. Retiring old suites closes several doors at once. For the wider family of these bugs, see our hub on cryptographic oracle attacks, and for more introductions start at the blog.

    The assumption that breaks

    The assumption behind every handshake is that if both ends are honest and both prefer strong crypto, they will end up using strong crypto. A downgrade attack breaks that by attacking the conversation, not the ciphers. The fix is to make the negotiation itself something an attacker cannot quietly rewrite, and to leave no weak option on the table to be selected.

    Asking whether a negotiation step is actually authenticated, rather than assuming it is because both ends meant well, is exactly the kind of question an autonomous researcher that tests assumptions is built to ask. Learn more about that approach on our about page.

    Frequently asked questions

    What is a cipher downgrade attack?

    It is an attack where a man in the middle interferes with the handshake that two ends use to agree on how to encrypt a connection, steering them toward the weakest option both still accept. The attacker strips the strong ciphersuites from the offered list or forces a weak one directly, so the connection ends up on crypto the attacker can break while both ends think they agreed on the best available.

    What are export grade ciphers and why do they still exist?

    Export grade ciphers use deliberately short keys, such as 512 bit RSA or 512 bit Diffie Hellman, that date back to 1990s rules limiting the strength of software shipped outside the United States. The rules were relaxed, but the weak suites stayed in code and server configs for years. They are breakable today, which is why a downgrade to one hands the session to an attacker.

    What were FREAK and Logjam?

    Both were cipher downgrade attacks in the same family. FREAK, tracked as CVE-2015-0204, forced connections onto export grade RSA so the 512 bit key could be factored. Logjam did the same with export grade Diffie Hellman, where shared 512 bit groups let an attacker precompute once and then break many handshakes cheaply. Different primitive, same downgrade shape.

    How do you prevent a cipher downgrade attack?

    Remove weak ciphersuites entirely so there is nothing weak to force, enforce a modern minimum protocol version so a rollback simply fails, and rely on the handshake authentication that modern TLS provides, where both ends verify a value computed over every handshake message so any tampering aborts the connection. Also remove insecure version fallback, which attackers abuse to force a downgrade.


    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.

  • Lucky Thirteen Attack Explained

    Lucky Thirteen Attack Explained

    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.

  • Constant Time Comparison: Why Equality Checks Leak Secrets

    Constant Time Comparison: Why Equality Checks Leak Secrets

    When your code checks a secret, a MAC tag, a session token, a password hash, the way it compares matters as much as the comparison itself. An ordinary equality check stops at the first byte that differs. That shortcut saves time, and the time it saves is exactly what leaks. Constant time comparison is the fix: compare two secrets in a way that takes the same time no matter where or whether they differ, so an attacker learns nothing from the clock.

    Why an ordinary equality check leaks

    Most string and byte comparisons are written to be fast. They walk both inputs left to right and return the moment they find a mismatch. In code that looks like this:

    def insecure_equal(a, b):
        if len(a) != len(b):
            return False
        for x, y in zip(a, b):
            if x != y:
                return False   # returns early on the first wrong byte
        return True

    That early return is the bug. If the first byte is wrong, the function returns almost instantly. If the first ten bytes match and the eleventh is wrong, it runs ten comparisons first, then returns. The more leading bytes an attacker guesses correctly, the longer the check takes. The running time is a direct readout of how many leading bytes matched.

    How timing turns into a forged tag

    Imagine a server that accepts a request only if a supplied MAC tag equals the tag it computes. The tag is 16 bytes. An attacker who cannot compute the tag can still forge one byte by byte, using only the response time.

    • Fix all 16 bytes to zero and try every value for the first byte, 256 requests. The correct first byte makes the server compare one extra byte before failing, so that guess is measurably slower.
    • Lock in the winning first byte, then sweep the second byte the same way. The right value adds another comparison and another small delay.
    • Repeat down the tag. Each byte costs at most 256 tries, so the whole 16 byte tag takes about 16 times 256 requests, a few thousand, instead of the 2 to the 128th an offline guess would need.

    An early exit comparison does not just say yes or no. It quietly tells the attacker how close they are, and anything that measures your closeness to a secret can be climbed one byte at a time.

    The per byte timing difference is tiny, often nanoseconds, and the network adds noise. Attackers beat the noise by averaging many requests per guess and using statistics to find the slow one. Over a local network, or against a tag checked many times, the signal is recoverable. This is the same family as the RSA timing attack, where the time a private key operation takes leaks the key, and the Lucky Thirteen attack, where tiny timing differences in MAC checking during decryption leak plaintext.

    What constant time comparison does instead

    A safe comparison never branches on the secret and never returns early. It looks at every byte of both inputs every time, accumulates the differences, and only reports the result at the end.

    def constant_time_equal(a, b):
        if len(a) != len(b):
            return False
        result = 0
        for x, y in zip(a, b):
            result |= x ^ y      # OR in any difference, never branch
        return result == 0

    Every byte is XORed and folded into result. A match contributes zero, a mismatch flips some bits and they stay flipped. The loop runs the full length regardless of where a difference is, so the time no longer depends on how many leading bytes matched. The clock says nothing useful, and the byte by byte climb dies.

    One caution about length

    Notice the length check returns early. Comparing strings of different length can leak the length of the secret through timing. When the length itself is sensitive, compare fixed size values, or hash both inputs to a fixed length first and compare the hashes, so length stops being observable.

    How to get constant time comparison right

    • Use the library function. Most platforms ship one built for this. Python has hmac.compare_digest, many languages have a constantTimeCompare or timingSafeEqual. Use it for every secret comparison rather than writing your own.
    • Compare the right things. MAC tags, session tokens, CSRF tokens, API keys, password reset tokens, and the output of a password hash verification all need it. Anything an attacker can submit and retry is a target.
    • Verify MACs, then decrypt. Check the authentication tag with a constant time compare before you act on the message. A leaky tag check is what the Lucky Thirteen family exploits.
    • Watch the compiler. An optimizer can turn your careful loop back into an early exit. Use the vetted library primitive, which is written and tested to stay constant time, rather than trusting hand written code to survive optimization.
    • Do not compare raw passwords. Store and compare a slow password hash, and use the verify function the hashing library provides, which is built to avoid this leak.

    This same discipline, never branching on a secret, is what stops the nonce bit leaks behind a biased nonce attack. For the wider family of these bugs, see our hub on cryptographic oracle attacks, or start with the vulnerability basics category.

    The assumption that breaks

    A missing constant time comparison is a clean example of correct logic with a hidden side channel. The equality check returns the right answer. It just takes a little longer when more of the guess is right, and that timing is a channel the code never meant to open. It hides in review because if tag == expected reads as obviously correct. Finding it means reasoning about how a secret is actually compared in the app and what an attacker can measure about that comparison, not just whether the check exists. That is what an autonomous researcher that tests assumptions is built to do. Read more on our about page.

    Frequently asked questions

    What is constant time comparison?

    It is a way to compare two secrets, such as a MAC tag or a token, that takes the same amount of time no matter where or whether they differ. Instead of returning at the first mismatched byte, it inspects every byte of both inputs and reports the result only at the end, so the running time reveals nothing about how many leading bytes matched.

    How does an ordinary equality check leak a secret?

    A normal comparison walks both inputs left to right and returns as soon as it finds a mismatch. The more leading bytes an attacker guesses correctly, the further the loop runs before failing, so the response takes slightly longer. That timing difference tells the attacker how close the guess is, which is exactly the information they need.

    Can timing really be used to forge a tag?

    Yes. An attacker fixes all bytes, sweeps the first byte through all 256 values, and keeps the one that makes the check measurably slower because it matched. They lock that byte in and move to the next. A 16 byte tag falls in about 16 times 256 requests instead of an infeasible offline guess. Network noise is beaten by averaging many requests per guess.

    How do I implement constant time comparison correctly?

    Use the platform function built for it, such as compare_digest in Python or timingSafeEqual elsewhere, rather than writing your own, because compilers can reintroduce an early exit. Apply it to every attacker submitted secret: MAC tags, tokens, API keys, and password hash checks. Be aware that comparing different length inputs can still leak length, so compare fixed size values when length is sensitive.


    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.

  • Compression Oracle Attack: Leaking Secrets by Length

    Compression Oracle Attack: Leaking Secrets by Length

    A compression oracle attack recovers a secret by watching how long a ciphertext is, not by breaking the cipher. It works whenever a system compresses data and then encrypts it, and an attacker can place their own guess next to the secret in the same compressed buffer. Compression shrinks repeated text, so when the attacker’s guess matches part of the secret, the two copies collapse together and the output gets shorter. A shorter ciphertext means the guess was right. This is the class behind the named CRIME and BREACH attacks, and it belongs to the wider family of cryptographic oracle attacks.

    How a compression oracle attack leaks a secret

    Start with how compression works. A common algorithm replaces a repeated run of bytes with a short back reference that says copy this many bytes from that far back. Two identical strings near each other compress to far less than two separate strings. The more the input repeats, the smaller the output.

    Now add encryption. The stream and counter based modes that protect most web traffic keep the plaintext length, give or take a small fixed amount. So the ciphertext length closely tracks the compressed length, which tracks how repetitive the plaintext was. The cipher hides the content but not the size, and size is the leak.

    The attacker needs two things in the same compressed buffer: a secret they want, such as a session token, and a field they control. Picture an invented app, Acme Notes, that reflects a search term into a page that also contains the user’s token, then compresses and encrypts the whole response. The attacker sets the search term to a guess at the token and measures the response size.

    secret in the buffer:  token=s3cr3t
    attacker controlled guess placed beside it:
    
      guess "token=a"   -> little overlap -> longer output
      guess "token=b"   -> little overlap -> longer output
      ...
      guess "token=s"   -> "token=s" now appears twice -> shorter output

    The guess that makes the response shortest shares the longest run with the secret, so its last character is almost certainly the next character of the token. Lock that character in, move to the next position, and repeat. The secret falls one byte at a time. The attacker never decrypts anything. They only compare lengths.

    Encryption hid every byte of the secret and leaked the one thing that still mattered, how many of those bytes the attacker had already guessed.

    Why compressing before encrypting is the trap

    The order is the whole problem. Compress then encrypt, and the ciphertext length reveals the plaintext redundancy. The named attacks differ only in where the compression sits. One targeted compression applied at the transport layer, below the application. The other targeted compression applied to web responses, which is common because it saves bandwidth. In both, a secret and attacker controlled data rode in the same compressed stream, and the length told the rest of the story.

    To make a guess cheap to test, the attacker usually needs many requests and a way to observe each response size, for example as a network observer or through a side channel that reveals length. The defense does not depend on blocking that view. It depends on making length say nothing about the secret.

    How to defend against it

    The fixes all come down to keeping the secret and the attacker’s input out of the same length signal.

    • Do not compress secret and attacker controlled data together. This is the core rule. If a response mixes a token with reflected user input, either do not compress it or do not put the secret there.
    • Turn off compression for sensitive responses. For pages and API replies that carry secrets, the bandwidth saved is not worth the leak. Disable it on those paths.
    • Separate contexts. Compress attacker influenced content in a buffer that contains no secret, and keep secrets in their own context that the attacker cannot add repeated text to.
    • Hide length. Add random padding so the output size no longer tracks the compressed size closely. This costs bandwidth and only blurs the signal, so treat it as a backstop, not a primary fix.
    • Make a guessed secret useless. Rotate or mask tokens per request, the way an anti forgery token is randomized each time, so that even a recovered value does not replay. A secret that changes every request cannot be rebuilt one slow byte at a time.

    The same length leak pairs naturally with stream ciphers, which preserve plaintext size exactly, a theme you can see from the other direction in keystream reuse. For where this sits among everyday web bugs, see vulnerability basics.

    The assumption that breaks

    A compression oracle attack is a clean example of a secret leaking through a channel nobody thought to protect. The cipher did its job. The developer added compression to save bytes, a reasonable choice on its own, and never considered that the output size would carry information about a secret sitting in the same buffer. Finding this means tracing what data shares a compression context and asking whether its length depends on something private, rather than scanning for a known bad function. That kind of reasoning about how an app really handles data is what an autonomous researcher like UnboundCompute is built to do. Read more on our about page.

    Frequently asked questions

    What is a compression oracle attack?

    It is an attack that recovers a secret by watching ciphertext length rather than breaking the cipher. When a system compresses data before encrypting it, and an attacker can place a guess next to the secret in the same compressed buffer, a matching guess makes the output shorter. A shorter ciphertext means the guess was right, so the secret leaks byte by byte.

    Why does compressing before encrypting leak a secret?

    Compression shrinks repeated text, so the output size depends on how repetitive the input was. Stream and counter based encryption keep the plaintext length, so the ciphertext size tracks the compressed size. The cipher hides the content but not the size, and when a secret and attacker input share a buffer, the size reveals how much they overlap.

    Are CRIME and BREACH compression oracle attacks?

    Yes. Both are named members of this class. They differ only in where the compression sits. One used compression applied at the transport layer below the application, and the other used compression applied to web responses. In both a secret and attacker controlled data rode in the same compressed stream, and the length told the rest.

    How do you defend against a compression oracle attack?

    Keep the secret and the attacker’s input out of the same length signal. Do not compress responses that mix a token with reflected user input, turn off compression for sensitive paths, separate compression contexts, add random padding to blur length as a backstop, and rotate or mask tokens per request so a recovered value cannot replay.


    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.

  • Key Commitment Attack: When AEAD Trusts Two Keys

    Key Commitment Attack: When AEAD Trusts Two Keys

    Most developers assume a ciphertext can only be read by the key that made it, and that any other key just produces garbage or a failed tag. A key commitment attack shows that assumption is false for the authenticated encryption modes in wide use today. With modest effort an attacker can craft a single ciphertext that decrypts to one valid message under key A and a different valid message under key B. The encryption never commits to a single key, and that gap enables message franking abuse and the fast password cracking of a partitioning oracle. It sits inside the broader family of cryptographic oracle attacks.

    What a key commitment attack breaks

    An AEAD mode, authenticated encryption with associated data, promises two things. Confidentiality: without the key you cannot read the plaintext. Integrity: you cannot change the ciphertext without the tag check failing. Both hold here. The attack does not touch either one.

    The property it breaks is a third one that many people assume comes for free but does not: commitment. A committing scheme guarantees that a given ciphertext and tag authenticate under at most one key. A non committing scheme makes no such promise, and the popular modes are non committing. You can hold a ciphertext that is perfectly valid under two keys you chose, each decrypting to a clean, attacker selected message.

    Why the common modes are not committing

    Take a widely used mode that builds its tag as a polynomial over the ciphertext, evaluated at a secret point derived from the key, then masked by another key derived value. The tag check passes when that computation lands on the tag attached to the message. For two different keys there are two different secret points and two different masks, but the attacker has free bytes in the message to play with. By choosing those free bytes, the attacker can solve a small equation so the tag computation lands on the same value under both keys at once. Nothing about this forges a tag without a key. The attacker owns both keys and is building a message that happens to satisfy both tag equations.

    This is the same structural weakness that shows up in AES GCM nonce reuse: the tag is algebra over known values, and algebra can be solved when the attacker controls enough of the inputs.

    Authentication proves a message was not altered by someone without the key. It does not prove which key that was, and a key commitment attack lives in the difference.

    What the gap enables

    A missing commitment looks harmless until you find a system that depends on one ciphertext meaning one key.

    Message franking abuse

    Some encrypted messaging systems let a recipient report an abusive message so a moderator can verify it really was sent. This relies on the ciphertext committing to its content under the message key. If an attacker can craft one ciphertext that decrypts to a harmless message under the key shown to the moderator and to an abusive message under the key the victim actually uses, the reporting system can be fooled. The abuse is real but the evidence does not hold up, or an innocent message is made to look abusive. Commitment is exactly the property franking needs, and non committing AEAD does not provide it.

    Partitioning oracles

    The same multi key ciphertext is the engine of the partitioning oracle attack. If one ciphertext can authenticate under a thousand candidate keys, a single accept answer from a server rules in a thousand guesses at once, collapsing a slow password search into a fast one. No commitment means the attacker can build that multi collision. Commitment makes it impossible.

    How to fix it

    The goal is to bind the ciphertext to exactly one key. Several approaches do this.

    • Use a committing AEAD construction. Newer modes are designed so a ciphertext authenticates under one key only. When your library offers a committing mode, prefer it for anything where keys are guessable or where a ciphertext must prove its origin.
    • Add an explicit key commitment. A generic transform computes a short commitment value from the key, a hash of the key or a derived tag, and stores it with the ciphertext. On decryption you recompute it and reject any mismatch. Now only the true key verifies.
    • Encrypt then MAC with a key bound MAC. Run a MAC whose output depends on the key over the ciphertext, and check it first. If the MAC genuinely binds the key, a foreign key fails before decryption.
    • Do not reuse one ciphertext across trust boundaries. If a design assumes a ciphertext means one sender or one key, make that assumption true with a commitment rather than hoping the mode provides it.

    For where this fits among common cryptographic usage bugs, see vulnerability basics.

    The assumption that breaks

    The root mistake is treating integrity and commitment as the same property. They are not. A mode can guarantee that a message was not tampered with while saying nothing about which key authenticated it. Spotting this means understanding what a system expects from its encryption, then checking whether the mode actually delivers that expectation, rather than matching a list of weak calls. That assumption testing is what an autonomous researcher like UnboundCompute is built to do. Read more on our about page.

    Frequently asked questions

    What is a key commitment attack?

    It is an attack that exploits the fact that common authenticated encryption modes do not commit to a single key. An attacker can craft one ciphertext that decrypts to a valid message under key A and a different valid message under key B. The attacker owns both keys and builds a message that satisfies both tag checks at once.

    Does a key commitment attack break confidentiality or integrity?

    Neither. Without a key you still cannot read the plaintext, and you still cannot alter a ciphertext without the tag check failing. The attack breaks a separate property called commitment, which is the guarantee that a ciphertext authenticates under only one key. Many people assume that comes for free, but the popular modes do not provide it.

    What damage does missing key commitment cause?

    Two main harms. It enables message franking abuse, where a crafted ciphertext reads as harmless to a moderator and abusive to the victim, so abuse reporting can be fooled. It also powers a partitioning oracle, where one ciphertext valid under many keys lets an attacker rule in many password guesses per query.

    How do you fix missing key commitment?

    Bind the ciphertext to one key. Use a committing AEAD construction where available, add an explicit commitment value computed from the key and checked on decryption, or run a key bound MAC over the ciphertext and verify it first. Do not reuse one ciphertext across trust boundaries without such a commitment.


    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.

  • Partitioning Oracle Attack: Cracking Passwords Fast

    Partitioning Oracle Attack: Cracking Passwords Fast

    A partitioning oracle attack is a fast way to guess a password or key by asking a server one clever question at a time. Instead of testing a single candidate per query, the attacker builds one ciphertext that decrypts successfully under a whole group of candidate keys at once. When the server reports that ciphertext as valid, the attacker learns the real key is somewhere in that group. One yes or no answer splits the search space in half, or better, which turns a slow online guessing attack into something close to a binary search. It is one member of the wider family of cryptographic oracle attacks.

    Where the partitioning oracle attack shows up

    The setting is password based encryption. A low entropy secret, a password or a PIN, is stretched into a key, and that key is used with an authenticated encryption mode to protect a message. Think of an encrypted backup, a messaging system that derives keys from a shared passphrase, or a token sealed with a key made from a user secret. Anywhere a server decrypts attacker supplied ciphertext and reveals whether it authenticated, you have the raw material for the attack.

    How one ciphertext decrypts under many keys

    You might assume a ciphertext belongs to exactly one key. Decrypt it under the wrong key and the authentication tag fails, so you get a reject. That intuition is wrong for the AEAD modes most systems use. As explained in the key commitment attack, those modes are not committing: with some effort an attacker can build one ciphertext and tag that verify as authentic under two different keys, and with more work, under hundreds or thousands of keys at once.

    A ciphertext that authenticates under a set S of candidate keys is called a key multi collision. When the server decrypts it with the real key, one of two things happens:

    • If the real key is in S, decryption authenticates and the server accepts.
    • If the real key is not in S, the tag fails and the server rejects.

    So the accept or reject answer tells the attacker whether the real key is inside the set they chose. That is the whole oracle.

    The search

    With that oracle in hand, the attacker runs a search over the candidate list. In the clean version it looks like this:

    candidates = all likely passwords        # say one million
    while more than one candidate remains:
        S  = half of the remaining candidates
        ct = build_multi_collision(S)        # one ciphertext valid for every key in S
        if server_accepts(ct):
            candidates = S                   # real key is in this half
        else:
            candidates = candidates - S      # real key is in the other half

    Building a multi collision for a very large set is expensive, so real attacks use a splitting factor smaller than half and eliminate a batch of candidates per query rather than exactly half. The principle holds either way: each query removes many guesses at once, not one.

    A normal guessing attack asks is it this one. A partitioning oracle asks is it any of these thousand, and gets a useful answer every time.

    Why this is so much worse than normal guessing

    Online password guessing is usually slow on purpose. You test one password per request, a rate limit caps you at a handful per minute, and a lockout stops you after a few misses. A candidate space of a million passwords is safe behind that wall because you can never send a million requests.

    A partitioning oracle tears the wall down. Each request now covers a large slice of the candidate space, so the number of requests drops from the size of the space to roughly its logarithm. A search that would have needed a million queries can finish in a few thousand, which fits inside many rate limits. The password was never weak in isolation. The encryption mode let the attacker ask about many passwords per question.

    How to defend against it

    The fixes attack both halves of the problem: the oracle itself and the speed it grants.

    • Use a key committing AEAD. This is the direct fix. A committing scheme makes a ciphertext valid under exactly one key, so a crafted multi collision cannot exist and a single query can only ever rule out a single key. See the key commitment attack for how commitment works and which constructions provide it.
    • Rate limit and lock out decryption attempts. Treat repeated failed decryptions like failed logins. Caps, backoff, and alerts shrink the window even if some oracle remains.
    • Do not expose the accept or reject bit. If an external caller never learns whether a ciphertext authenticated, there is no oracle to query. Fail the same way for every bad input.
    • Raise the entropy of the secret. Partitioning helps most against small candidate spaces. A long random key leaves nothing to enumerate, though this is not an option when humans pick the secret.
    • Bind context into the key. Mix a per user salt or a server side secret into the key derivation so one crafted ciphertext cannot apply across accounts.

    The assumption that breaks

    The mistake here is believing that one valid decryption equals one correct key. That holds only if the encryption commits to its key, and the common modes do not. For background on this class of usage bug, see vulnerability basics. Finding a partitioning oracle means noticing that a decryption endpoint leaks a validity bit, then asking whether the mode behind it commits to a single key. That is reasoning about how an app uses a primitive, not matching a known bad call, and it is the kind of question an autonomous researcher like UnboundCompute is built to ask. Read more on our about page.

    Frequently asked questions

    What is a partitioning oracle attack?

    It is a method for guessing a password or key quickly by crafting one ciphertext that decrypts validly under a whole group of candidate keys at once. When a server accepts that ciphertext, the attacker learns the real key is somewhere in the group, so a single query rules in or out many guesses instead of one.

    Why is a partitioning oracle faster than normal password guessing?

    Normal online guessing tests one password per request, so a large candidate space stays safe behind rate limits. A partitioning oracle covers many candidates per request, so the number of queries drops from the size of the space to roughly its logarithm. A search that would need a million queries can finish in a few thousand.

    What makes a partitioning oracle attack possible?

    The encryption mode must fail to commit to a single key. Common AEAD modes allow an attacker to build one ciphertext and tag that authenticate under many keys at once, called a key multi collision. If a ciphertext could only ever verify under one key, each query would rule out just one guess and the attack would collapse.

    How do you stop a partitioning oracle attack?

    Use a key committing AEAD so a ciphertext is valid under exactly one key, which is the direct fix. Also rate limit and lock out repeated failed decryptions, avoid exposing the accept or reject bit to external callers, and bind a per user salt into key derivation so one crafted ciphertext cannot apply across accounts.


    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.

  • Keystream Reuse Attack: The Two Time Pad Problem Explained

    Keystream Reuse Attack: The Two Time Pad Problem Explained

    Stream ciphers are fast and simple. They turn a secret key and a nonce into a long pseudo random keystream, then XOR that keystream with your plaintext to make ciphertext. The whole design rests on one rule: never produce the same keystream twice. A keystream reuse attack is what happens when you break that rule, by reusing a key and nonce together, or by using a one time pad twice. The keystream cancels out and an attacker reads your plaintext without ever touching the key.

    How a stream cipher hides data

    A stream cipher takes the key K and a nonce N and stretches them into a keystream KS, a sequence of bytes that looks random. Encryption is a single XOR:

    C = P XOR KS        # encrypt: ciphertext is plaintext masked by keystream
    P = C XOR KS        # decrypt: the same mask removes itself

    This works because XOR is its own inverse. Apply the same mask twice and you get back what you started with. The keystream is the mask, and it is only safe as long as each mask is used exactly once. The same idea covers the one time pad, which is a stream cipher whose keystream is truly random and as long as the message. A one time pad is provably unbreakable, but only if the pad is never reused.

    Why a keystream reuse attack works

    Suppose you encrypt two messages, P1 and P2, with the same key and the same nonce. Both get the identical keystream KS. Now XOR the two ciphertexts together:

    C1 = P1 XOR KS
    C2 = P2 XOR KS
    
    C1 XOR C2 = (P1 XOR KS) XOR (P2 XOR KS)
              = P1 XOR P2 XOR (KS XOR KS)
              = P1 XOR P2

    The keystream appears twice and cancels itself. What the attacker holds is P1 XOR P2, the XOR of the two plaintexts, and no key was needed to get there. This is called the two time pad problem, because a one time pad used twice stops being one time and leaks exactly this.

    Reuse the keystream and the ciphertexts stop hiding anything from each other. XOR them and the mask vanishes, leaving the two messages layered on top of each other.

    From XOR of plaintexts to the plaintexts themselves

    Holding P1 XOR P2 is already a serious leak, and it usually unravels completely. If the attacker knows or can guess any part of one message, that part of the other drops out by XOR. Protocol messages are full of known structure: fixed headers, JSON keys like {"user":, HTTP verbs, predictable field names. Each guessed byte in one message reveals the aligned byte in the other. This technique is called crib dragging: slide a likely word across the combined stream and keep the positions where both results read as sensible text. With a few messages sharing one keystream, the whole set falls.

    How the keystream gets reused

    Nobody sets out to reuse a keystream. It slips in through the nonce.

    • A fixed or hardcoded nonce. A constant nonce in the source, often a block of zeros left in from a test, means every message under that key shares a keystream.
    • A counter that resets. A device that keeps its nonce counter in memory and restarts at zero after a reboot or crash re emits nonces it already used.
    • A random nonce that is too short. Draw a 64 bit nonce at random under one key and collisions arrive sooner than you expect from the birthday bound. A single collision is one reused keystream.
    • One key copied across machines. Clone a virtual machine that holds the key and nonce state, or share a key across a fleet without coordinating nonces, and two encryptors emit the same nonce under the same key.

    This is the same failure shape as AES GCM nonce reuse, which is a stream cipher in counter mode with an authentication tag bolted on. Repeat the nonce and the counter mode keystream repeats exactly as described here, so GCM leaks the XOR of plaintexts and, worse, the authentication key too. A related mistake with block cipher modes is covered in the predictable IV attack, where a guessable initialization vector undermines chained modes.

    How to defend against a keystream reuse attack

    The defenses are concrete and they stack.

    • Never reuse a nonce under a key. Treat that as a hard invariant. Everything below serves it.
    • Use a counter nonce that never goes backward. A per message counter that strictly increases and never wraps avoids collisions entirely. Persist it so a reboot cannot rewind it.
    • If you use random nonces, make them wide and cap messages per key. Use at least 96 bits from a real CSPRNG and rotate the key well before the birthday bound puts a collision in reach.
    • Rotate keys often. A fresh key resets the whole nonce space and limits the damage from any one slip.
    • Coordinate nonces across machines. Give each encryptor that shares a key a distinct nonce prefix so their ranges cannot overlap, or give each its own key.
    • For a true one time pad, use each pad once and destroy it. The security proof depends on it. A pad reused even once collapses to the two time pad problem above.

    For the wider family of these bugs, see our hub on cryptographic oracle attacks, or start with the vulnerability basics category.

    The assumption that breaks

    A keystream reuse attack is not a weakness in the cipher. The keystream generator can be strong and the XOR is exactly right. The failure is a broken assumption about how the cipher is used, that every nonce under a key is unique. It hides in a constant left in the code, a counter that resets, or a key copied to one machine too many, and the encrypt call looks correct on its own line. Finding it means reasoning about how a nonce is actually produced across the whole system and whether anything can make two of them collide. That is the kind of work an autonomous researcher that tests assumptions is built to do. Read more on our about page.

    Frequently asked questions

    What is a keystream reuse attack?

    It is what happens when a stream cipher produces the same keystream twice, usually because a key and nonce pair was reused. Since encryption is plaintext XORed with the keystream, XORing the two ciphertexts cancels the shared keystream and leaves the XOR of the two plaintexts. The attacker recovers the messages without ever learning the key.

    What is the two time pad problem?

    A one time pad is unbreakable only if the pad is used exactly once. Use the same pad for two messages and XORing the two ciphertexts removes the pad entirely, leaving the XOR of the two plaintexts. That is the two time pad problem. The same math applies to any stream cipher whose keystream gets reused.

    How does an attacker turn the XOR of two plaintexts into the actual messages?

    They use crib dragging. Any known or guessable text in one message, such as a fixed header or a JSON key, reveals the aligned bytes of the other by XOR. Sliding likely words across the combined stream and keeping positions where both sides read as sensible text peels the messages apart, especially when several messages share one keystream.

    How do I prevent keystream reuse?

    Never reuse a nonce under a key. Prefer a counter nonce that strictly increases and never resets, and persist it across reboots. If you use random nonces, make them at least 96 bits from a strong generator and rotate keys well before a collision becomes likely. Coordinate nonces across machines that share a key, and for a true one time pad use each pad only once.


    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.

  • Sweet32 Attack: How Small Block Ciphers Leak Plaintext

    Sweet32 Attack: How Small Block Ciphers Leak Plaintext

    The sweet32 attack is what happens when a cipher with a small block size runs for too long under one key. Ciphers like 3DES and Blowfish use a 64 bit block, and once you encrypt enough traffic, two ciphertext blocks are bound to collide by pure probability. In CBC mode a collision leaks the XOR of two plaintext blocks, and if one of them is a secret that repeats in every request, the attacker reads it out. This post explains the birthday bound that makes it inevitable and why the fix is to retire 64 bit block ciphers.

    The birthday bound in one paragraph

    The birthday problem says that in a group of 23 people, two probably share a birthday, far sooner than intuition expects. The same math applies to block cipher outputs. For a block size of n bits, you expect a collision among random looking ciphertext blocks after roughly 2^(n/2) of them, not 2^n. That square root is the whole story. For a 64 bit block, the bound is about 2^32 blocks. For a 128 bit block like AES, it is about 2^64, which no real connection reaches.

    64 bit block  -> collision near 2^32 blocks  (reachable)
    128 bit block -> collision near 2^64 blocks  (not reachable)

    Two to the 32 blocks of 8 bytes each is around 32 gigabytes of traffic under a single key. A long running connection that moves that much data is ordinary, and that is the window the attack needs.

    Why the sweet32 attack leaks plaintext

    Collisions alone would just be a curiosity. CBC mode turns them into plaintext recovery. Recall how CBC builds each ciphertext block:

    C[i] = E(key, P[i] XOR C[i-1])

    The block cipher is a permutation, so equal outputs mean equal inputs. If two ciphertext blocks collide, C[i] = C[j], then their inputs matched too:

    P[i] XOR C[i-1] = P[j] XOR C[j-1]

    Rearrange it and the attacker, who can see all the ciphertext, learns something about the plaintext:

    P[i] XOR P[j] = C[i-1] XOR C[j-1]   (both known)

    The right side is two ciphertext blocks the attacker already captured, so the XOR of two plaintext blocks drops out for free. Now the trap closes. If one of those plaintext blocks is a fixed secret that the client sends in every single request, say an auth token or a session cookie, then it is the same value in countless positions. The attacker waits for that secret block to collide with a block they know, and the XOR hands them the secret directly.

    A small block size is not a flaw in the cipher’s math. It is a fuel gauge. Burn enough data under one key and a collision is not a risk, it is a schedule.

    Picture a VPN or an HTTPS session for a SaaS app called Acme Notes that negotiated a 64 bit block cipher and keeps the connection open for hours. A browser replays the same session cookie on every request inside that tunnel. An attacker who can nudge the client into generating a lot of traffic, and who watches the encrypted stream, collects blocks until the cookie block collides with a known block. The cookie falls out, and no part of the cipher was actually broken.

    How a connection ends up here

    The weakness is a configuration choice that outlived its safety margin.

    • Legacy cipher suites left enabled. A server keeps 3DES in its TLS configuration for compatibility with very old clients, and a downgrade lands a modern client on it.
    • Long running tunnels on one key. A VPN or a database link that stays up for hours and never rekeys gives the attacker the volume they need.
    • A repeated secret in the stream. Any value that appears in the same place in every message, like a cookie or a fixed header, is the ideal target block.

    Getting a victim onto the weak cipher in the first place often rides on a cipher downgrade attack, which nudges a handshake down to the worst option both sides will accept. The underlying idea, that a repeated or colliding value leaks the XOR of two plaintexts, is the same one behind a keystream reuse attack. You can read more foundations under vulnerability basics, and the full set sits in our cryptographic oracle attacks hub.

    How to fix it

    The fix is to stop feeding the fuel gauge.

    • Retire 64 bit block ciphers. Disable 3DES, Blowfish, and other 64 bit block ciphers in TLS, VPN, and application configs. Prefer AES, which has a 128 bit block and pushes the birthday bound out of reach.
    • Prefer an authenticated 128 bit mode. AES GCM or ChaCha20 Poly1305 give a large effective state and add integrity, so you are not leaning on block size alone.
    • Rekey long connections. If you cannot move off a 64 bit cipher yet, cap the data per key far below the birthday bound and rekey well before you approach 32 gigabytes on one key.
    • Shorten session lifetimes. Tear down and renegotiate long tunnels so no single key ever sees enough traffic to collide.
    • Remove repeated secrets from the stream where you can. The attack needs a constant target block, so anything that reduces a fixed secret repeating in place lowers the payoff.

    The shape is the same as the other entries in this cluster. The cipher is doing its job. The failure is an assumption, that one key can encrypt unlimited data, which a 64 bit block quietly makes false after a few tens of gigabytes. The config line that enables the old cipher looks harmless on its own.

    Spotting this means reasoning about how an application builds and protects a long encrypted stream, which cipher it settled on, and how much data runs under a single key before anything rotates. That is the kind of assumption an autonomous researcher is built to test. Read more on our about page.

    Frequently asked questions

    What is the Sweet32 attack?

    It is a birthday bound attack on ciphers with a 64 bit block, such as 3DES and Blowfish, used in CBC mode over a long connection. After about two to the 32 blocks under one key, two ciphertext blocks are likely to collide. In CBC a collision reveals the XOR of two plaintext blocks, and if one is a secret that repeats in every request, the attacker recovers it without breaking the cipher.

    Why does a 64 bit block size make this possible?

    The birthday bound says collisions appear after roughly two to the power of half the block size, not the full size. For a 64 bit block that is about two to the 32 blocks, which at 8 bytes each is near 32 gigabytes of traffic under one key, a volume a long running tunnel reaches. A 128 bit block like AES pushes the bound to two to the 64, which no real connection gets near.

    How does a collision leak plaintext in CBC?

    The block cipher is a permutation, so equal ciphertext blocks mean equal inputs. If two ciphertext blocks are equal, then each plaintext block XORed with its preceding ciphertext block must also be equal. Rearrange that and the XOR of the two plaintext blocks equals the XOR of the two preceding ciphertext blocks, which are values the attacker already captured. So the XOR of two plaintext blocks falls out for free. When one block is a known value and the other is a repeated secret, the secret is recovered.

    How do you prevent the Sweet32 attack?

    Retire 64 bit block ciphers. Disable 3DES and Blowfish in TLS, VPN, and application configs and prefer AES, which has a 128 bit block. Use an authenticated mode like AES GCM or ChaCha20 Poly1305. If you must keep a 64 bit cipher for now, rekey long connections and cap the data per key far below the birthday bound, and shorten session lifetimes so no key sees enough traffic to collide.


    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.