Author: UnboundCompute

  • ECB Mode Weakness: Why Identical Blocks Leak and Forge

    ECB Mode Weakness: Why Identical Blocks Leak and Forge

    The ecb mode weakness is the oldest and most visual mistake in block cipher usage. ECB encrypts every block of plaintext on its own, with no chaining and no IV, so two identical plaintext blocks always produce the same ciphertext block. That single fact leaks the structure of your data and lets an attacker rearrange or splice ciphertext to forge fields they were never meant to set. This post shows what leaks, how cut and paste forgery works, and why the fix is to stop using ECB.

    How ECB encrypts

    ECB stands for Electronic Code Book. The name is literal: it treats the cipher like a lookup table. Split the plaintext into blocks, encrypt each block independently under the key, concatenate the results.

    C[0] = E(key, P[0])
    C[1] = E(key, P[1])
    C[2] = E(key, P[2])

    There is no IV and no dependence between blocks. Compare that to CBC, which chains each block into the next. In ECB the same plaintext block always maps to the same ciphertext block, every time, across every message under that key. That determinism is the root of the whole problem.

    The ecb mode weakness you can see

    The famous demonstration is an image. Encrypt a bitmap of a simple shape with ECB, and the outline is still visible in the ciphertext. Flat regions of one color are blocks of identical plaintext, so they become blocks of identical ciphertext, and the pattern survives encryption. The pixels are scrambled but the shape is not hidden. ECB encrypts the values, never the relationships between them.

    The same leak applies to any structured data, not just images. A record with repeated fields, a protocol with fixed headers, a database column with a small set of values: wherever plaintext blocks repeat, ECB advertises it. An observer can count distinct values, spot repeated commands, and map the shape of data they cannot read.

    ECB keeps every repetition in your plaintext perfectly intact. It hides the bytes and publishes the pattern, which for structured data is often the part you needed to protect.

    Cut and paste forgery

    Leaking structure is the passive half. The active half is that ECB blocks are position independent, so an attacker can move them around. A ciphertext block that decrypts to some value decrypts to that same value no matter where you place it. That turns encrypted blocks into interchangeable tiles.

    Take a SaaS app called Acme Notes that issues a session token whose plaintext is field aligned to 16 byte blocks:

    block 0: email=bob@ac.me&
    block 1: role=user;id=042

    Now suppose the attacker can register an account and control part of the plaintext, for example their email address. They register an address padded so that one full block of their own token is exactly:

    role=admin;id=09

    ECB encrypts that block the same way it would in any token, so the attacker captures its ciphertext. Then they take their ordinary session token and swap their real role block for the captured role=admin block. The server decrypts the spliced token block by block, with no chaining to notice the seam, and reads a valid admin role. No key was broken. The attacker assembled a forged plaintext out of legitimately encrypted tiles.

    This is the same class of trust failure as a cbc bit flipping attack, where an attacker edits decrypted data without the key, and it rhymes with a predictable iv attack, where missing randomness lets equal plaintext leak through equal ciphertext. All three come from using a cipher without asking whether the output can be tampered with or reassembled. You can browse more of these basics on our blog, and the wider set lives in our cryptographic oracle attacks hub.

    How to fix it

    There is no safe tuning of ECB for general data. The fix is to replace it.

    • Never use ECB for anything with structure. That means almost everything: tokens, records, messages, files. The only time identical plaintext blocks are not a leak is when your data is a single block or is already uniformly random, which is rare and easy to get wrong.
    • Use an authenticated mode. AES GCM or ChaCha20 Poly1305 give you a fresh nonce per message, so identical plaintext no longer maps to identical ciphertext, and an authentication tag, so a spliced or reordered ciphertext is rejected before it is ever decrypted. That kills both the structure leak and the cut and paste forgery in one move.
    • If you only need confidentiality, still authenticate. Even a randomized mode like CBC with a good IV is open to tampering without a MAC. Pairing encryption with integrity is the baseline, not an extra.
    • Bind context into the authentication. Include the record type, the user, or the message position in the associated data, so a block or message cannot be lifted from one context and replayed in another.
    • Reach for a library default. A modern crypto library hands you an authenticated mode by default and makes ECB the awkward, clearly labeled option. Take the default.

    Notice the pattern one more time. AES is not weak. ECB is a correct, well specified mode that does exactly what it says. The weakness is the assumption that encrypting each block is the same as protecting the data, when in fact ECB preserves every repetition and lets blocks be shuffled freely. Code review misses it because the encrypt call looks ordinary on its line.

    Catching this means reasoning about how an application builds a ciphertext, whether equal inputs can produce equal outputs, and whether anyone ever verifies that the blocks arrived in one piece. That assumption testing is exactly what an autonomous researcher is built to do. Read more on our about page.

    Frequently asked questions

    What is the ECB mode weakness?

    ECB encrypts each block of plaintext independently, with no IV and no chaining, so two identical plaintext blocks always produce the same ciphertext block under a given key. That determinism leaks the structure of the data, the reason the encrypted image of a simple shape still shows its outline, and it lets an attacker rearrange or splice ciphertext blocks because each block decrypts to the same value wherever it is placed.

    Why does the ECB penguin image still show the picture?

    Flat regions of one color are long runs of identical plaintext blocks. ECB maps each identical block to the same ciphertext block, so those runs stay identical after encryption and the outline survives. ECB hides the byte values but keeps every repetition, and for images the repetition is the shape.

    What is cut and paste forgery in ECB?

    Because ECB blocks are position independent, a ciphertext block that decrypts to a value keeps that value anywhere you put it. An attacker who can influence part of the plaintext can make the system encrypt a block they want, such as one reading role=admin, then splice that captured ciphertext block into another token in place of the real role block. The server decrypts it with no chaining to catch the seam and trusts the forged field.

    How do you fix the ECB mode weakness?

    Stop using ECB for any data with structure, which is almost all data. Use an authenticated mode such as AES GCM or ChaCha20 Poly1305, which adds a fresh nonce so identical plaintext no longer maps to identical ciphertext, and an authentication tag so a spliced or reordered ciphertext is rejected before decryption. Bind context into the associated data so blocks cannot be replayed across messages.


    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.

  • Predictable IV Attack: How a Guessable IV Breaks CBC

    Predictable IV Attack: How a Guessable IV Breaks CBC

    A predictable iv attack goes after a detail that looks harmless: the initialization vector that CBC mode mixes into the first block. If an attacker can guess the IV a message will use before it is sent, and can influence even a little of the plaintext, they can turn encryption into a yes or no oracle and confirm their guesses about the secret one piece at a time. This post explains why an unpredictable IV is part of the security promise, not a throwaway value, and how a predictable one breaks it.

    What the IV is supposed to do

    CBC encrypts the first plaintext block like this:

    C[0] = E(key, P[0] XOR IV)

    The IV exists so that encrypting the same message twice produces different ciphertext. Without it, two identical login requests would encrypt to identical bytes, and an observer would learn they were the same. That property has a name, semantic security: ciphertext should leak nothing about plaintext, not even whether two messages match. The IV is what delivers it, and the IV only works if the attacker cannot predict it in advance.

    Why a predictable iv attack breaks semantic security

    There are two flavors, and both come from the IV being known ahead of time.

    A reused IV leaks equality

    If every message uses the same fixed IV, then identical first blocks always encrypt to identical ciphertext. The first block behaves just like ECB mode: equal plaintext in, equal ciphertext out. An attacker watching traffic can tell when two messages start with the same block, which is enough to fingerprint a repeated command, a known header, or a session that keeps resending the same secret.

    A predictable IV enables a confirmation oracle

    This is the sharper problem, the class behind the chosen boundary attacks on older TLS. Suppose an attacker can both see the IV that will be used next and inject part of the plaintext that gets encrypted alongside a secret they want to read. They pick a guess for one unknown byte, then craft their injected block so that, if the guess is right, its input to the cipher equals the secret block’s input to the cipher.

    guess input:  P_guess XOR IV_next
    secret input: S       XOR IV_secret

    Because the attacker knows both IVs, they choose P_guess so these two inputs are equal exactly when P_guess carries the correct secret value. Equal inputs to the same block cipher give equal ciphertext blocks. So the attacker watches the two ciphertext blocks: a collision means the guess was right, no collision means it was wrong. One bit of feedback per try, and with the boundary lined up so only one byte is unknown at a time, the full secret falls out byte by byte.

    An unpredictable IV keeps encryption honest by making every first block a fresh surprise. A predictable IV lets an attacker line up a guess against a secret and read the answer off a collision.

    Picture a SaaS app called Acme Notes that keeps a CBC encrypted auth token in a cookie and reuses a counter as the IV, so the next IV is simply the last one plus one. An attacker who can trigger encryptions and observe the ciphertext now knows each IV before it is used. That is the exact condition the confirmation oracle needs. The token was never read directly, yet it leaks.

    How predictable IVs sneak into code

    Nobody sets out to ship a guessable IV. It arrives through shortcuts that look reasonable:

    • A constant IV. A developer hardcodes a zero IV or a fixed string because the API demanded one and random felt like extra work. Now every message shares it.
    • A counter or timestamp. An IV built from a sequence number or the clock is unique but fully predictable, which defeats the confirmation defense even though it never repeats.
    • Chaining the last ciphertext block. Older protocols used the previous message’s final ciphertext block as the next IV. It is unique, but it is visible on the wire, so the attacker knows the next IV before sending.
    • A weak random source. A non cryptographic generator, or one seeded from the time, produces IVs an attacker can reconstruct.

    This sits right next to two other CBC failures. A cbc bit flipping attack abuses the same first block XOR to tamper with plaintext rather than read it, and aes gcm nonce reuse shows the same lesson in an authenticated mode, where repeating the value that should be unique collapses the guarantee. You can find more of these foundations under vulnerability basics, and the broader family sits in our cryptographic oracle attacks hub.

    How to fix it

    The rule is short: the IV must be unpredictable to anyone who does not hold the key, and fresh for every message.

    • Generate the IV from a CSPRNG. Draw a new random IV for each message from the platform secure random source, never from a counter, a clock, or a previous ciphertext block.
    • Send the IV in the clear, but pick it before you reveal it. The IV does not need to be secret. It needs to be unknown at the moment the attacker is choosing their injected plaintext. A per message random IV satisfies that.
    • Prefer an AEAD mode. AES GCM or ChaCha20 Poly1305 take a nonce and add an authentication tag. They sidestep the chosen boundary problem and catch tampering at the same time, as long as you honor the unique nonce rule.
    • Authenticate the ciphertext. Even with a good IV, unauthenticated CBC is still open to tampering. Pair it with a MAC or move to AEAD so integrity is covered too.
    • Do not reuse IV and key together. A fresh key resets the whole space. Rotating keys limits how much any single weakness can expose.

    The pattern is familiar by now. AES is fine and CBC is fine. The break is an assumption in the glue code, that the IV is just a formality you can fill with anything. Pick a predictable value and the whole mode quietly loses its meaning, while the encrypt call on the page still looks correct.

    Finding this kind of flaw means reasoning about how an application builds each ciphertext, where its IV comes from, and whether an attacker could know that value ahead of time. That is exactly the assumption testing an autonomous researcher is built to do. Read more on our about page.

    Frequently asked questions

    What is a predictable IV attack?

    It is an attack on CBC mode where the initialization vector can be guessed before a message is sent. CBC XORs the IV into the first plaintext block, and the IV exists so that encrypting the same message twice looks different. If an attacker knows the next IV and can inject part of the plaintext, they can craft a block that collides with a secret block only when their guess is correct, turning encryption into a yes or no oracle that leaks the secret one byte at a time.

    Why does an IV need to be unpredictable?

    Semantic security means ciphertext should leak nothing about plaintext, not even whether two messages match. The IV delivers that by making each first block fresh. A fixed or reused IV makes identical first blocks encrypt identically, leaking equality like ECB. A unique but guessable IV, such as a counter or the previous ciphertext block, lets an attacker line up a guess against a secret and confirm it, which is the chosen boundary class of attack.

    How do predictable IVs end up in real code?

    Through shortcuts that look fine. A hardcoded zero or constant IV, an IV built from a counter or timestamp, reusing the previous message’s last ciphertext block as the next IV, or drawing the IV from a weak non cryptographic random source. Each one is either repeated or guessable, and either condition is enough to undermine the mode.

    How do you prevent a predictable IV attack?

    Generate a fresh random IV for every message from a cryptographic random source, never from a counter, clock, or prior ciphertext. The IV can travel in the clear, it just has to be unknown while the attacker is choosing their input. Better still, use an AEAD mode like AES GCM or ChaCha20 Poly1305, which handles the unique value and also authenticates the ciphertext so tampering is caught too.


    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.

  • CBC Bit Flipping Attack: Tampering Without the Key

    CBC Bit Flipping Attack: Tampering Without the Key

    A cbc bit flipping attack turns missing integrity protection into a tampering tool. The attacker cannot read your plaintext and does not know the key, yet by flipping chosen bits in one ciphertext block they flip the exact same bits in the next decrypted block. If that block holds a field like a role or a flag, they can rewrite it to their advantage. This post shows why CBC hands that control to an attacker and how authenticated encryption shuts it down.

    How CBC decryption actually works

    CBC stands for Cipher Block Chaining. When encrypting, each plaintext block is XORed with the previous ciphertext block before the block cipher runs, which chains the blocks together. The very first block is XORed with an initialization vector, the IV, instead of a previous block. Decryption runs the same chain in reverse:

    P[i] = D(key, C[i]) XOR C[i-1]
    P[0] = D(key, C[0]) XOR IV

    Look closely at those two lines. The decrypted plaintext of block i is the raw block cipher output XORed with the previous ciphertext block. That previous block is data the attacker can see, and in a cookie or a token on the wire, often data the attacker can change. That is the whole weakness.

    Why a cbc bit flipping attack works

    XOR has one property that matters here. Flip a single bit of an input and you flip the matching bit of the output, nothing else moves. In CBC, C[i-1] feeds straight into the XOR that produces P[i]. So flipping bit 6 of ciphertext block i-1 flips bit 6 of plaintext block i after decryption. The attacker steers that bit without ever touching the key.

    There is a price. The block you tamper with, C[i-1], still runs through the block cipher on its way to P[i-1], so that earlier block decrypts to random garbage. The attack pays off when the garbled block sits somewhere the application ignores or parses loosely, while the block you care about lands on a field that matters. Targeting block 0 avoids the cost entirely, because there you flip the IV, and the IV is not a decrypted block.

    A worked example

    Imagine a note sharing app called Acme Notes. After login it gives the browser an encrypted session cookie. The plaintext of its first 16 byte block is exactly:

    admin=0&user=bob

    The app encrypts this with CBC and trusts whatever it decrypts. The attacker wants the admin value to read 1 instead of 0. The character 0 is byte index 6 of block 0. Since block 0 is XORed with the IV, the attacker flips the matching byte of the IV and nothing else changes:

    delta  = '0' XOR '1' = 0x30 XOR 0x31 = 0x01
    IV'[6] = IV[6] XOR 0x01

    With that one byte of the IV flipped, block 0 now decrypts to admin=1&user=bob. The username, the structure, every other byte stays intact because the IV only touches the first block. The attacker just granted themselves admin without the key, without reading a single plaintext byte, and without any error from the cipher.

    If the target field sits in block 2 instead, the same trick works on ciphertext block 1, at the cost of block 1 decrypting to garbage. Attackers pick layouts where that collateral block is discarded or tolerated, and plenty of real formats oblige.

    The missing idea: integrity

    CBC on its own gives you confidentiality and nothing else. It was never designed to tell the receiver whether the ciphertext arrived unchanged. Decryption happily turns any bytes into some plaintext, garbled or not, and returns it. The application then parses that output and acts on it. Nothing in the pipeline ever asked the question that matters.

    Encryption that is not authenticated answers “can I read this” but never “did anyone change this”. A cbc bit flipping attack lives entirely in that second, unasked question.

    This is the same family of failure behind a padding oracle attack, where an error message about malformed padding leaks plaintext one byte at a time. Both start from the same root cause: a CBC ciphertext that the receiver decrypts before it ever checks whether the bytes are genuine. It also rhymes with a predictable iv attack, where a weak or reused IV undermines CBC from the other end. You can read more of these building blocks under vulnerability basics, and the wider pattern of chosen ciphertext tampering sits in our cryptographic oracle attacks hub.

    How to fix it

    The fix is to authenticate the ciphertext, so any tampered bit is caught and the whole message is rejected before decryption ever runs. Concrete steps:

    • Use an authenticated encryption mode. AES GCM or ChaCha20 Poly1305 produce a tag alongside the ciphertext. A flipped bit changes the tag check, the receiver sees the mismatch, and it refuses the message. Bit flipping stops being useful.
    • If you are stuck with CBC, use encrypt then MAC. Compute a MAC such as HMAC SHA256 over the IV and the full ciphertext, and send it alongside. On receipt, verify the MAC first. Only if it matches do you decrypt. Never decrypt and then check, and never trust a value you decrypted before the MAC passed.
    • Verify with a constant time comparison. Compare the received MAC to the computed one with a comparison that does not leak timing, so an attacker cannot tune forged tags byte by byte.
    • Cover everything the receiver trusts. The authenticated data must include the IV and any associated context, otherwise an attacker tampers with whatever you left outside the tag.
    • Do not invent your own construction. Reach for a vetted library that exposes an authenticated mode by default, so the correct order is the only order on offer.

    The shape of this bug is worth keeping in mind. AES is not broken. CBC is doing exactly what it was specified to do. The failure is an assumption in the surrounding code: that a ciphertext which decrypts cleanly must be a ciphertext nobody altered. That assumption is false, and code review misses it because each encrypt and decrypt call looks correct on its own line.

    This is the kind of flaw that shows up when you reason about how an application builds a ciphertext and whether it ever actually verifies integrity before acting on the result, which is exactly what an autonomous researcher that tests an app’s assumptions is built to check. Read how we think about it on our about page.

    Frequently asked questions

    What is a CBC bit flipping attack?

    It is a tampering attack on CBC ciphertext that has no integrity protection. Because each plaintext block is the block cipher output XORed with the previous ciphertext block, an attacker who flips a bit in one ciphertext block flips the exact same bit in the next decrypted block. They can rewrite a field such as a role or a flag without knowing the key and without reading the plaintext. The cost is that the block they tamper with decrypts to garbage.

    How can an attacker change plaintext without the key?

    XOR flips the matching output bit when you flip an input bit. In CBC the previous ciphertext block is XORed into the current decrypted block, so controlling bits of that previous block controls the same bits of the next plaintext. To change the first block cleanly an attacker flips bits of the IV instead, which touches only that block and leaves every other byte intact.

    Why does CBC allow this at all?

    CBC provides confidentiality but no integrity. Decryption turns any bytes into some plaintext and hands it back without ever checking whether the ciphertext was changed on the way. The application then parses and trusts that output. Nothing in the flow asks whether the message is genuine, so flipped bits pass straight through.

    How do you prevent a CBC bit flipping attack?

    Authenticate the ciphertext so any change is detected before decryption. Use an authenticated mode like AES GCM or ChaCha20 Poly1305, which rejects a tampered message by its tag. If you must keep CBC, use encrypt then MAC: compute an HMAC over the IV and ciphertext, verify it in constant time, and only decrypt if it matches. Never decrypt first and check later.


    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.

  • Biased Nonce Attack: How a Few Predictable Bits Leak a Signing Key

    Biased Nonce Attack: How a Few Predictable Bits Leak a Signing Key

    Signature schemes like ECDSA and Schnorr need a fresh random number for every signature. That number is the nonce, sometimes called k. Most people know that repeating a nonce is fatal. Fewer know that you do not need a repeat to lose the key. A biased nonce attack recovers the private key when the nonce is only slightly predictable, a handful of bits that are always zero, or a generator that leans one way. Gather enough signatures and a lattice turns that small leak into the whole secret.

    How a nonce is supposed to behave

    In ECDSA each signature uses a nonce k drawn uniformly at random from the range 1 to n-1, where n is the order of the curve group. The signature is a pair (r, s) where r comes from the point k*G and s = k^-1 * (h + r*d) mod n. Here h is the message hash and d is the private key. The nonce must stay secret, and it must be uniform. Any structure you can see in k is structure an attacker can use.

    Reuse is the loud failure

    If you sign two different messages with the same k, both signatures share the same r, and two equations in two unknowns solve directly for d. That is the classic break covered in ECDSA nonce reuse. It is one repeated value and the key falls out in a line of algebra.

    Bias is the quiet failure

    A biased nonce attack is subtler and, in practice, more common. The nonces are all different, so nothing looks wrong. But they are not uniform. Maybe the top 8 bits are always zero because the generator returns a value a little too short. Maybe a modulo reduction skews the distribution. Maybe a few low bits are fixed by a buggy conversion. Each signature now quietly encodes a linear relation between the key and a small unknown. One such relation tells you almost nothing. Hundreds of them, stacked together, pin the key exactly.

    You do not need to see the nonce. You only need to know that a few of its bits are not random, and that the scheme will sign as many messages as you ask.

    Turning a small bias into the key with a lattice

    Each signature gives you the public equation s = k^-1 * (h + r*d) mod n. Rearrange it to express the nonce k as a known value plus an unknown multiple of the private key, all mod n. If k were full size this tells you nothing useful. But if you know k is small, say the top bits are zero so k < 2^248 on a 256 bit curve, then you know a linear combination of known quantities and the secret is unusually small.

    That is the hidden number problem. Write one such constraint per signature as a row, and you get a lattice, a grid of integer combinations, in which the private key corresponds to an unusually short vector. A lattice reduction algorithm such as LLL or BKZ finds that short vector, and out of it drops d.

    # one row per signature, schematically
    # t_i and u_i are public, built from (r_i, s_i, h_i)
    #   k_i = t_i * d + u_i   (mod n),   with k_i known to be small
    # stack many rows, reduce the lattice, read d from the short vector
    t_i = r_i * s_i_inv mod n
    u_i = h_i * s_i_inv mod n

    How many signatures you need scales with how much bias there is. A large bias, like the top 16 bits always zero, can fall with a few dozen signatures. A tiny bias of 2 or 3 known bits needs hundreds to a few thousand, but the attack still works. More signatures and more known bits both make the lattice easier. This is the same engine behind partial nonce leak results that have broken real deployments where a side channel exposed a couple of bits of k per signature, the kind of timing leak covered in the RSA timing attack.

    Where the bias comes from

    Nobody designs a biased nonce on purpose. It leaks in through ordinary mistakes.

    • A short or truncated random draw. Reading fewer bytes than the curve needs, or generating a value and masking off high bits, leaves the top bits predictable.
    • Bad modular reduction. Taking a random number mod n without care skews small values slightly more likely, a measurable bias at scale.
    • A weak or seeded generator. A non cryptographic RNG, or one seeded with low entropy at boot, produces values with structure an attacker can model.
    • Side channels. Timing or cache behavior during the scalar multiplication can leak a few bits of k, which feeds the lattice directly even when the generator is fine.

    How to defend against a biased nonce attack

    • Use deterministic nonces. RFC 6979 derives k from the private key and the message hash with HMAC, so the nonce is reproducible, uniform, and free of RNG bias. The same message always gives the same signature, which is fine, and two different messages never collide.
    • Consider a hedged or synthetic nonce. Mix fresh randomness into the deterministic derivation so a single bad RNG read cannot bias the output, and a fault cannot repeat a nonce.
    • Audit the RNG. If you must draw k at random, use a real CSPRNG, draw the full width, and reduce into range without skew. Seed it properly before first use.
    • Write the scalar multiplication in constant time. Stop the side channel that leaks nonce bits. See constant time comparison for the same idea applied to secret comparisons.
    • Prefer schemes and libraries that make this the default. Modern signing libraries ship RFC 6979 or hedged nonces out of the box. Use the high level call rather than feeding your own k.

    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 biased nonce attack is a clean example of math that is sound being undone by a usage detail. ECDSA is fine. The signing equation is fine. The failure is the belief that k is uniform when a short read or a skewed reduction made it lean. The library call looks correct on its line, so the bias never shows up in review, and only a lattice over many signatures reveals it. Finding this kind of flaw means reasoning about how a nonce is actually produced and used in the app, not just whether the function was called. That is what an autonomous researcher that tests assumptions is built to do. Read more on our about page.

    Frequently asked questions

    How is a biased nonce attack different from nonce reuse?

    Nonce reuse means two signatures share the exact same nonce, which solves for the private key directly from two equations. A biased nonce attack never repeats a nonce. The nonces are all different but not uniform, so a few of their bits are predictable. That small leak tells you almost nothing per signature, but stacked across hundreds of signatures and solved with a lattice, it recovers the full key.

    How many signatures does the attack need?

    It depends on how much bias there is. A large bias, such as the top 16 bits of the nonce always being zero, can fall with a few dozen signatures. A tiny bias of two or three known bits needs hundreds to a few thousand. More signatures and more known bits both make the underlying lattice reduction easier to solve.

    What makes a nonce biased in real code?

    Common causes are a random draw that reads too few bytes, masking off high bits, a modular reduction that skews small values, a weak or poorly seeded random number generator, and side channels such as timing that leak a couple of nonce bits per signature. Any of these leaves structure in the nonce that a lattice can exploit.

    How do I prevent a biased nonce attack?

    Use deterministic nonces as described in RFC 6979, which derive the nonce from the private key and message hash with HMAC so there is no RNG bias. Consider a hedged nonce that also mixes in fresh randomness. Audit any RNG you do use, write the scalar multiplication in constant time to stop side channel leaks, and prefer libraries that do all of this by default.


    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.

  • Invalid Curve Attack: How a Bad Point Leaks Your Private Key

    Invalid Curve Attack: How a Bad Point Leaks Your Private Key

    Elliptic curve cryptography sits under TLS key exchange, digital signatures, and secure messaging. Its safety rests on a quiet assumption: every point your code does math with actually lies on the curve you chose. An invalid curve attack breaks that assumption. If your implementation accepts a point from the other side and never checks that it belongs to the curve, an attacker can hand you a point that lives on a different, weaker curve. Over many exchanges, the math leaks your private key one small piece at a time.

    Why a point has to be on the curve

    A short Weierstrass curve is the set of points (x, y) that satisfy y^2 = x^3 + a*x + b mod p, plus a point at infinity. Your private key is a secret scalar d. In a key exchange you receive the peer’s public point P and compute a shared secret as the scalar multiple d*P, then run it through a key derivation function.

    The catch is in the arithmetic. The formulas for point addition and doubling on a short Weierstrass curve use the coefficient a and the prime p, but they never use b. The curve constant b is only pinned down by the points you start with. So if you start with a point that does not satisfy your equation, the ladder still runs. It just computes the scalar multiple in the group of some other curve.

    The math that does not notice

    Say your curve uses constant b, but the attacker sends a point (x, y) that instead satisfies y^2 = x^3 + a*x + b' for some different b'. Your scalar multiplication routine reads x, y, a, and p, does exactly what it always does, and returns d*P computed on the attacker’s curve. Nothing errors out. The numbers look fine. You derived a key from a secret that now lives somewhere you did not intend.

    Picking a curve full of small subgroups

    Here is where it turns into key recovery. A well chosen curve has a large prime group order, so scalars are hard to guess. The attacker instead looks for a nearby curve whose order has small factors. On that curve they find a point P of small order r, say order 5. Because P has order 5, the result d*P can only be one of 5 possible points, and which one depends solely on d mod 5.

    The attacker now learns d mod 5 by seeing which shared secret you ended up with. They do not see the secret directly, but they see something derived from it: an authentication tag that verifies, a handshake that completes, or an error that does not. Each completed exchange confirms one guess. Five tries at most pin down d mod 5.

    Skip the on curve check and each handshake stops being a secret exchange. It becomes a yes or no question about your private key, and the attacker gets to ask as many as you will answer.

    Then they repeat with points of order 7, 11, 13, and so on, collecting d mod r for many small coprime values of r. Once the product of those r values exceeds the size of d, the Chinese Remainder Theorem reconstructs the full private key. A few dozen chosen exchanges can be enough.

    How it shows up in code

    The bug is almost always a missing line. A vulnerable exchange reads the peer point straight off the wire and multiplies:

    point = parse_peer_point(request)      # x and y taken from the message
    shared = scalar_mult(our_private, point)   # no on-curve check here
    key = kdf(shared)
    send_confirmation(key)                 # tells the attacker if the guess was right

    The fix is the check that is not there: confirm the point satisfies the curve equation, reject the point at infinity, and confirm the coordinates are in range before you multiply by your secret. One validation call stops the whole family of attacks.

    How to defend against an invalid curve attack

    The defenses are concrete and they stack. None of them needs new cryptography.

    • Validate every received point. Plug (x, y) into y^2 = x^3 + a*x + b mod p and reject anything that does not satisfy it. Also reject the point at infinity and coordinates outside [0, p).
    • Check the subgroup. Confirm the point has the correct order, or use a curve with cofactor 1 so a valid on curve point cannot sit in a small subgroup. Multiply by the cofactor and reject the identity when the cofactor is larger than 1.
    • Prefer x only ladders. Montgomery curve designs like the one behind X25519 compute with the x coordinate alone and are built so that points map into the intended group, which removes most of the footgun. Use them where you can.
    • Use a vetted library and its high level API. Hand rolled point parsing is where the check gets dropped. A reviewed library validates inputs for you, so let it.
    • Validate even ephemeral keys. People skip checks on ephemeral ECDH because the key is short lived. The attack works per exchange, so a throwaway key still leaks bits. Validate every time.

    This is a close cousin of the small subgroup confinement attack, where a valid point in a small subgroup leaks key bits the same way, and it rhymes with ECDSA nonce reuse, another case where sound elliptic curve math is undone by one missing usage rule. For more of these, see our hub on cryptographic oracle attacks, and the vulnerability basics category for the groundwork.

    The assumption that breaks

    An invalid curve attack is not a flaw in elliptic curve cryptography. The curve is strong and the scalar multiplication is correct. The failure is an input that was trusted without being checked, a point that was never confirmed to be on the curve before a secret touched it. That kind of gap hides in code review because the scalar_mult call reads as correct on its own line. Finding it means asking what the code assumes about its inputs and then testing whether an attacker can make that assumption false. That is exactly the kind of reasoning an autonomous researcher that tests assumptions is built to do. Read more on our about page.

    Frequently asked questions

    What is an invalid curve attack?

    It is an attack on an elliptic curve implementation that accepts a point without checking it lies on the chosen curve. The attacker sends a point that belongs to a different, weaker curve. Scalar multiplication still runs because the point addition formulas do not use the curve constant b, so the secret key gets multiplied into a group the attacker controls, leaking key bits over repeated exchanges.

    How does the attack recover the whole private key?

    The attacker sends points of small order, so the result of the scalar multiplication depends only on the private key modulo that small order. Each completed exchange confirms the key value modulo a small number. By collecting the key modulo many small coprime values and combining them with the Chinese Remainder Theorem, the attacker reconstructs the full private key from a few dozen chosen exchanges.

    How do I prevent an invalid curve attack?

    Validate every received point before you multiply it by a secret. Confirm it satisfies the curve equation, reject the point at infinity, and check the coordinates are in range. Verify the point is in the correct subgroup or use a curve with cofactor 1. Prefer x only ladder designs like X25519 and use a vetted library that validates inputs for you.

    Does using an ephemeral key make my code safe?

    No. The attack works one exchange at a time, so a short lived ephemeral key still leaks bits of whatever secret touches the untrusted point. People skip validation on ephemeral keys because they feel disposable, but you must validate every received point every time, ephemeral or not.


    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.

  • Small Subgroup Confinement Attack: Leaking a Diffie Hellman Key

    Small Subgroup Confinement Attack: Leaking a Diffie Hellman Key

    A small subgroup confinement attack leaks a Diffie Hellman private key by sending a group element that can only produce a handful of possible shared secrets. When a party does not validate the public value it receives, an attacker hands it an element of small order. The shared secret is then trapped in a tiny set, often just two or a few values, and by seeing which one the victim landed on, the attacker learns the victim’s private key modulo a small number. Repeat with a few such elements, combine the answers, and the full key falls out. The math is sound. The missing check is the whole bug. This post shows the group theory, a small worked example, and the fix.

    How Diffie Hellman is supposed to work

    Classic Diffie Hellman runs in the multiplicative group of integers modulo a prime p. There is a public base g. Each party picks a secret exponent, one side a and the other b, and sends the other their public value, A = g^a mod p and B = g^b mod p. Each side raises what it received to its own secret, and both arrive at the same shared secret g^(a*b) mod p. Knowing g, p, and a public value, recovering the secret exponent is the discrete logarithm problem, which is infeasible in a large well chosen group.

    That safety rests on a quiet assumption: that the value you received really is a normal element of the big group, produced by raising g to some exponent. Nothing in the raw protocol forces that. The number on the wire is just a number modulo p, and the attacker chooses it.

    Why a small subgroup confinement attack works

    The order of an element is how many times you multiply it by itself before you get back to one. In the group modulo p, the whole group has order p - 1, and every factor of p - 1 gives rise to a subgroup of that size. If p - 1 has small factors, the group contains elements of small order, elements that cycle through only a few values before repeating.

    Here is the lever. Suppose the attacker sends the victim an element Y whose order is a small number t, instead of a genuine public value. The victim, not checking, computes the shared secret as Y^b mod p. But Y only has t distinct powers, so Y^b can be only one of t values, and which one it is depends only on b mod t. The attacker has confined the shared secret to a set of size t and, by learning which value the victim used, reads off b mod t.

    The attacker does not guess the private key. They send a number that collapses the shared secret into a few options, then ask the protocol which option came out.

    How does the attacker see which value the victim landed on? Usually the protocol does something observable with the shared secret, such as sending a message authentication code computed under it, or encrypting a known value. The attacker tries all t candidate secrets, checks which one matches what the victim sent, and now knows b mod t.

    A small worked example

    Let p = 23, so the group has order p - 1 = 22 = 2 * 11. That factoring means the group holds elements of order 2 and of order 11. The element 22 has order 2, because 22 is the same as -1 modulo 23, and squaring it gives 1. Say the victim’s static private key is b. The attacker sends Y = 22. The victim computes:

    shared = Y^b mod 23 = (-1)^b mod 23
           = 1   if b is even
           = 22  if b is odd

    Only two outcomes are possible. By checking the victim’s key confirmation against both, the attacker learns one bit: the parity of b. Next the attacker sends an element of order 11, whose powers cycle through 11 values. Testing all 11 against the confirmation reveals b mod 11. Now they hold b mod 2 and b mod 11, and the Chinese remainder theorem glues those into b mod 22, which pins the private key down completely for this tiny group. Real groups are vastly larger, but the recipe is identical: peel off the key modulo each small factor, then recombine.

    Static keys are the real danger

    The attack pays off when the victim reuses a private exponent across many handshakes, a static Diffie Hellman key. Each malicious element leaks the key modulo another small factor, and because the key never changes, the leaks stack up until the Chinese remainder theorem recovers the whole thing. With a fresh ephemeral key per handshake, a single malicious element leaks at most a little about a value the attacker will never see again, so the confinement attack cannot accumulate. For an invented messaging backend, call it Acme Chat, that pins one long lived Diffie Hellman key and skips validation, a patient attacker recovers that key across a series of ordinary looking handshakes.

    The fix: validate the public value, pick the right group

    Every defense either rejects small order elements or removes them from the group entirely.

    • Validate the received value. Reject anything that is not a real member of the intended large subgroup. Confirm the value is strictly between 1 and p - 1, and check that raising it to the large prime order q gives 1, which proves it lives in the right subgroup and not in a small one.
    • Use safe primes. A safe prime has the form p = 2q + 1 with q also prime. Then p - 1 = 2q has no small factors except 2, so the only tiny subgroup has order 2, and a single cheap check for the values 1 and p - 1 closes it.
    • Use a prime order group. Work inside the subgroup of prime order q, which by definition has no smaller subgroups at all, so there is no small order element to send.
    • Prefer ephemeral keys and named groups. A new exponent per handshake stops leaks from accumulating, and standardized named groups are chosen to avoid these pitfalls.

    This is the same shape as the elliptic curve version, where a point off the intended curve lands in a weak group, covered in our invalid curve attack post. It also rhymes with other key material bugs such as ecdsa nonce reuse, and with decryption breaks that come from skipping a check, like the bleichenbacher attack. For the wider group, see our hub on cryptographic oracle attacks, or start with vulnerability basics.

    The assumption that breaks

    One assumption carries the whole attack: that a value arriving over the wire is a well behaved member of the group, just because the protocol expects one. It is not, unless you check. An attacker supplies a number of their choosing, and if the code never asks whether that number belongs where it was put, the shared secret quietly collapses into a set small enough to search. You find a bug like this by asking what a system takes for granted about its inputs and whether anything can make it false, not by scanning for a known bad string. That is what an autonomous researcher built to test assumptions does: reason about how an app uses a primitive and whether a missing validation lets a crafted value leak a secret. Read more on our about page.

    Frequently asked questions

    What is a small subgroup confinement attack?

    It is an attack on Diffie Hellman where an attacker sends a group element of small order instead of a real public value. The victim that skips validation computes the shared secret from it, but that secret can now be only one of a few values, and which one depends on the victim’s private key modulo a small number. Seeing the result leaks the key modulo that number, and several such elements recover the whole key.

    Why does an element of small order leak private key bits?

    The order of an element is how many distinct powers it has before repeating. If the attacker sends an element of order t, then the victim’s value raised to its private key b can be only t possible outcomes, determined entirely by b mod t. The attacker tries all t candidates against something the victim does with the secret, such as a key confirmation, and reads off b mod t.

    Why are static Diffie Hellman keys especially at risk?

    A static key is reused across many handshakes, so each malicious element leaks the key modulo another small factor and the leaks accumulate. The Chinese remainder theorem then glues those partial answers into the full private key. A fresh ephemeral key per handshake stops the accumulation, because any leak is about a value that is never used again.

    How do you prevent a small subgroup confinement attack?

    Validate the received value: confirm it lies strictly between 1 and p - 1 and that raising it to the large prime order gives 1, proving it belongs to the intended subgroup. Use safe primes of the form p = 2q + 1 so there are no small factors to abuse, or work in a prime order group that has no small subgroups at all, and prefer ephemeral keys and standardized named groups.


    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.

  • RSA Timing Attack: Recovering a Key From Response Latency

    RSA Timing Attack: Recovering a Key From Response Latency

    An rsa timing attack recovers a private key by measuring how long the private operation takes, not by breaking any math. The decryption or signing step runs a modular exponentiation with the secret exponent, and the time that exponentiation takes depends on the bits of the key. Feed the server many ciphertexts, measure the latency of each response with care, and the pattern in those timings leaks the key one piece at a time. The cipher stays intact. The clock is the leak. This post shows where the timing comes from, how measurement turns it into key bits, and why blinding and constant time code close it.

    What an rsa timing attack actually measures

    An rsa timing attack is a side channel attack. A side channel is any way a secret leaks through a physical or observable property of the computation rather than through its output. Time is the easiest one to measure over a network. The RSA private operation is m = c^d mod N, where d is the secret exponent. Raising a number to a large exponent is done by repeated squaring and multiplying, and the exact sequence of operations, plus how long each one runs, is tied to the value of d and to the number being processed. If the code does not actively flatten those differences, the total time carries information about the secret.

    Why the time depends on the key

    Start with the textbook exponentiation, square and multiply. To compute c^d, the code walks the bits of d from top to bottom. At every bit it squares the running value. When the bit is a one, it also multiplies by c. When the bit is a zero, it does not.

    for each bit of d, high to low:
        x = x * x mod N          # always
        if bit == 1:
            x = x * c mod N      # only on a 1 bit

    So a one bit costs a square and a multiply, while a zero bit costs only a square. The two take different amounts of time. If you could see the total time of one exponentiation and nothing else, you would already know roughly how many one bits the exponent has. Worse, real implementations let the timing of each step depend on the operands, which ties the timing to where the one bits sit, not just how many there are.

    A second source is subtler and is what made remote attacks practical. Fast implementations reduce numbers using Montgomery multiplication, which sometimes performs one extra subtraction at the end, called an extra reduction, to bring the result back into range. Whether that extra step happens depends on the value being multiplied. By choosing ciphertexts that sit just above or just below a multiple of one of the secret prime factors of N, an attacker makes the frequency of extra reductions swing in a way that reveals whether their guess at a key bit was right.

    The output of an RSA decryption tells you nothing about the key. The time it took to produce that output tells you a little, and a little, measured a million times, is the whole key.

    From a few microseconds to the full key

    No single measurement reveals a key bit. Network jitter, scheduling, and cache effects bury the signal in noise. The attack beats the noise with repetition and statistics. The attacker picks ciphertexts designed to make the timing of a correct guess differ from the timing of a wrong guess, sends each one many times, and averages. The true value of a bit shows up as a consistent gap of a few microseconds between the two guesses, visible only once thousands of samples are stacked up.

    Researchers demonstrated in 2003 that this works across a local network against a common RSA library, recovering the factorization of the modulus, which is the whole private key, from timing alone. The practical recipe is to recover the secret prime factor one bit at a time: guess the next bit, send ciphertexts near the boundary that bit controls, measure whether decryption sped up or slowed down, keep the guess that matches, and move on. For an invented document service, call it Acme Sign, that signs with RSA and answers each request as fast as it can, an attacker who can time responses precisely can, with enough patience, walk the private key out of the server without a single malformed request. Every query looks like an ordinary client asking for a signature.

    The fix: blinding and constant time code

    There are two defenses, and strong systems use both.

    Blinding breaks the correlation

    Blinding makes the timing useless by hiding which number the private key is actually operating on. Before decrypting, the code picks a fresh random value r, multiplies the ciphertext by r^e mod N, runs the private operation on that blinded input, and then divides the result by r to recover the true plaintext:

    blinded = c * r^e mod N
    result  = blinded^d mod N
    m       = result * r^-1 mod N

    Because r is random and different every time, the attacker cannot choose the exact value the exponentiation sees, and they cannot align their careful near boundary ciphertexts with anything. The timing still varies, but it no longer correlates with a ciphertext the attacker controls, so the statistics collapse into noise.

    Constant time removes the signal at the source

    The deeper fix is to make the private operation take the same time no matter what the key or the data is. That means exponentiation routines that perform the same sequence of squares and multiplies regardless of whether a key bit is zero or one, and that do the extra Montgomery reduction every time, or never, rather than conditionally. Constant time code does not branch on secret values and does not index memory at secret dependent addresses. The same discipline protects the small comparison steps around a signature, which is why we wrote a whole post on constant time comparison. Timing leaks are not unique to RSA, and the same measurement mindset underlies the timing variant of a padding oracle attack.

    • Turn on RSA blinding so the attacker cannot aim chosen ciphertexts at the exponentiation.
    • Use a constant time exponentiation that runs the same steps for every key bit.
    • Never branch or index memory on a secret value anywhere in the private path.
    • Keep to well reviewed libraries rather than writing your own modular exponentiation.

    For the wider family, see our hub on cryptographic oracle attacks, and for a related break where RSA leaks through its padding rather than its clock, read about rsa signature forgery. A good starting point for the basics is our blog.

    The assumption that breaks

    One assumption holds this bug up: that a system leaks only through what it says, not through how long it takes to say it. Developers reason about inputs and outputs and rarely about the clock, so a private operation that races to finish quietly publishes the secret it was protecting. You find a flaw like this by asking what an attacker can measure about a response besides its contents, and whether any of it moves with a secret. That is what an autonomous researcher built to test assumptions does: reason about how an app uses a primitive and whether a secret dependent response, including its timing, leaks. Read more on our about page.

    Frequently asked questions

    What is an RSA timing attack?

    It is a side channel attack that recovers an RSA private key by measuring how long the private operation takes instead of breaking the math. The decryption or signing step runs a modular exponentiation with the secret exponent, and the time it takes depends on the key bits and the number being processed. Measured carefully across many requests, those timings leak the key.

    Why does RSA decryption time depend on the secret key?

    The simple square and multiply method squares at every exponent bit but multiplies only on a one bit, so one bits and zero bits cost different amounts of time. Fast Montgomery reduction adds another signal, because it sometimes performs an extra subtraction whose frequency depends on the operand. An attacker who chooses inputs near the right boundary makes those differences reveal key bits.

    Can an RSA timing attack work over a network?

    Yes. A single measurement is buried in network jitter and scheduling noise, but sending the same crafted ciphertext many times and averaging exposes a steady gap of a few microseconds between a correct and an incorrect guess. Researchers demonstrated in 2003 that this recovers the factorization of the modulus across a local network, which is the full private key.

    How do you prevent an RSA timing attack?

    Use both blinding and constant time code. Blinding multiplies the input by a fresh random value before the private operation and divides it out afterward, so the attacker cannot aim chosen ciphertexts at the exponentiation. Constant time code runs the same steps for every key bit and never branches or indexes memory on a secret, which removes the signal at its source. Rely on well reviewed libraries rather than rolling your own.


    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.

  • RSA Signature Forgery: Forging a Signature With a Loose Verifier

    RSA Signature Forgery: Forging a Signature With a Loose Verifier

    RSA signature forgery is the trick of producing a value that a careless verifier accepts as a genuine signature, without ever holding the private key. It does not break RSA. It breaks the code that checks the padding around the signed hash. When a library parses PKCS#1 v1.5 signatures loosely and the key uses a small public exponent such as three, an attacker can craft a number whose cube happens to look like a correctly padded hash, and a lax verifier waves it through. This post shows the padding the check relies on, why loose parsing plus a low exponent is forgeable, and how to shut it.

    How PKCS#1 v1.5 signature verification is meant to work

    RSA signing is the private operation and verifying is the public one. To sign, the owner hashes the message, wraps the hash in a fixed padding structure, and raises the whole thing to the private exponent. To verify, anyone raises the signature to the public exponent e and checks that the result is that exact padded structure around the expected hash. The padded block for a key whose modulus is k bytes looks like this:

    00 01 FF FF FF ... FF 00 [ DigestInfo ] [ hash ]

    The block starts with 00 01, then a long run of FF bytes that fills the block out to full width, then a single 00 separator, then a small DER structure called DigestInfo that names the hash algorithm, then the hash itself. The FF padding exists precisely to push the hash to the far right and leave no free space. A correct verifier rebuilds this entire block from the message and compares it byte for byte against what the signature decrypted to. If even one byte differs, it rejects.

    Where RSA signature forgery comes from

    The flaw is a verifier that does not rebuild the whole block and compare it. Instead it parses. It scans from the left, sees 00 01, skips forward over the FF bytes, finds the 00 separator, reads the DigestInfo, reads as many hash bytes as the algorithm needs, and then stops. The bug is in that word stops. A loose parser does not check that the hash sits hard against the right edge of the block, and it does not check that nothing follows. It treats any bytes after the hash as none of its business.

    That single omission is the opening. If trailing bytes are ignored, the attacker no longer has to match the full width of the modulus. They only have to produce a number whose top bytes spell out 00 01 FF ... 00 DigestInfo hash, while the bottom bytes can be anything at all. And with a low public exponent, producing such a number is a cube root away.

    A correct verifier asks whether the signature equals the one right value. A loose verifier asks only whether the signature starts the right way, and that is a question an attacker can answer without the key.

    The low exponent cube root

    Many RSA keys use a public exponent of three because it makes verification fast. Verifying then means cubing the signature and reducing modulo N. Here is the forging idea in plain terms. The attacker writes down the target block they want the cube to resemble: the 00 01 prefix, a short run of FF, the separator, the DigestInfo, and the real hash of their chosen message, all packed into the high end of a number, with the low end left as free space. They then take the ordinary integer cube root of that target and round it to a whole number. Call that s.

    Because the structured part lives in the high bytes, cubing s reproduces those high bytes exactly, and whatever falls out in the low bytes is garbage the attacker never had to control. The forged signature is simply s. When a loose verifier cubes it, reads the prefix, walks to the hash, confirms the hash matches the message, and ignores the trailing garbage, it declares the signature valid. No modular inverse, no private exponent, nothing secret. For an invented update service, call it Acme Updater, that ships with a loose verifier and an exponent of three, an attacker can forge the signature on a malicious package and the client accepts it as signed by the vendor. This is the same family as the classic attack disclosed by Bleichenbacher, and it rhymes with the decryption side covered in our bleichenbacher attack writeup: both turn sloppy PKCS#1 v1.5 handling into a break.

    Why this keeps happening

    You would think rebuilding and comparing the full block is the obvious way to verify, and it is, but parsing grew popular because it is flexible. A parser that walks the structure tolerates slightly different DigestInfo encodings across algorithms and libraries, so it felt convenient. That tolerance is the bug. Over the years several widely used libraries shipped parsers that skipped the right side checks, and the same forgery resurfaced under new names each time a low exponent key met a lenient verifier. The pattern is stable: permissive input handling on a security decision, and an attacker who lives in the gap between what the format requires and what the code enforces.

    The fix: verify strictly, raise the exponent

    Every defense closes the gap between parsing and rebuilding.

    • Rebuild and compare, do not parse. Construct the full expected padded block from the message hash, then compare it against the decrypted signature byte for byte across the entire modulus width. This alone kills the forgery, because the trailing garbage no longer passes unnoticed.
    • Check every field if you must parse. Confirm the FF run fills the block, the separator is present, the DigestInfo is exactly the expected DER with no alternative encodings, and the hash sits flush against the right edge with zero trailing bytes.
    • Use a larger public exponent. A common choice is 65537. A higher exponent makes the cube root style construction impractical, because the signature would have to match far more of the block at once.
    • Prefer a modern scheme and verified code. RSA PSS uses randomized padding that resists this class, and a carefully reviewed library that rebuilds the block removes the footgun from your own hands.

    For the wider group this sits in, see our hub on cryptographic oracle attacks, and for another way RSA leaks through its implementation rather than its math, read about the rsa timing attack. If you want the grounding first, start with vulnerability basics.

    The assumption that breaks

    One quiet assumption carries this bug: that accepting a signature which merely looks right in its opening bytes is the same as accepting one that is right. It is not. Verification is a decision about authenticity, and a decision that checks the easy part and trusts the rest is an open door. You find a bug like this by comparing what a format demands against what a verifier actually enforces, field by field, not by scanning for a bad string. That is what an autonomous researcher built to test assumptions does: reason about how an app uses a primitive, such as an RSA verifier, and whether a permissive check lets a crafted value through. Read more on our about page.

    Frequently asked questions

    What is RSA signature forgery?

    It is producing a number that a verifier accepts as a genuine RSA signature without holding the private key. RSA itself is not broken. The attack works when the code that checks PKCS#1 v1.5 padding around the signed hash parses the structure loosely instead of rebuilding and comparing the whole block, so a crafted value that only looks right at the start slips through.

    Why does a public exponent of three make forgery easier?

    Verifying with exponent three means cubing the signature. An attacker builds the target block they want, with the padding and real hash packed into the high bytes and the low bytes left free, then takes the integer cube root and rounds it. Cubing that value reproduces the structured high bytes exactly while the low bytes come out as garbage a loose verifier ignores. A larger exponent such as 65537 makes this impractical.

    What does a loose verifier do wrong?

    It scans from the left for the 00 01 prefix, skips the FF padding, finds the separator, reads the DigestInfo and the hash, and then stops. It never checks that the hash sits flush against the right edge or that nothing follows it. Those trailing bytes are where the forgery hides. A strict verifier rebuilds the full padded block and compares every byte.

    How do you prevent RSA signature forgery?

    Rebuild the expected padded block from the message hash and compare it byte for byte across the whole modulus, rather than parsing field by field. Use a larger public exponent such as 65537, prefer the randomized RSA PSS scheme, and rely on a carefully reviewed library. The MITRE entry CWE-347, improper verification of cryptographic signature tracks the general class.


    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.

  • The Bleichenbacher Attack: an RSA Padding Oracle That Recovers Plaintext

    The Bleichenbacher Attack: an RSA Padding Oracle That Recovers Plaintext

    A bleichenbacher attack recovers the plaintext behind an RSA ciphertext without ever holding the private key, using a server that quietly reveals whether a decrypted message had valid PKCS#1 v1.5 padding. The attacker never factors the modulus. They send thousands of slightly altered ciphertexts, read one yes or no answer each time, and let that answer narrow the hidden message down to a single value. Daniel Bleichenbacher published this in 1998, and the same oracle keeps returning in modern TLS stacks. This post walks the padding, the oracle, the math, and the fix.

    What a bleichenbacher attack actually is

    A bleichenbacher attack is an adaptive chosen ciphertext attack on RSA with PKCS#1 v1.5 encryption padding. RSA is not broken here. The modulus is never factored and the private exponent never leaks. The target is a small piece of behavior wrapped around the cipher: the step that checks whether the recovered bytes are formatted correctly and reacts differently when they are not. That difference is the oracle, and one fact about RSA turns it into a decryption machine.

    RSA encryption is c = m^e mod N and decryption is m = c^d mod N. The property the attack rides on is that RSA is multiplicative. If you multiply a ciphertext by another number raised to the public exponent, the decrypted plaintext is multiplied by that same number:

    (c * s^e)^d = m * s mod N

    So an attacker who does not know m can still produce a ciphertext whose plaintext is m times any chosen value s. They steer the hidden plaintext by multiplication, then ask the oracle what the result looked like.

    The padding that becomes a leak

    PKCS#1 v1.5 encryption wraps the real message in a fixed structure the width of the modulus. For a key whose modulus is k bytes, an encryption block looks like this before it is raised to the public exponent:

    00 02 [ at least 8 random nonzero bytes ] 00 [ message ]

    The first two bytes are always 00 02. A message that starts with those two bytes after decryption is called conforming. The receiver checks for them, strips the random padding up to the 00 separator, and keeps the rest. If the first two bytes are wrong, the receiver knows the block was malformed and reacts, often with an error, sometimes with a timing difference, sometimes with a dropped connection. Any observable difference between conforming and not conforming is the oracle.

    In TLS that used RSA key exchange, the client encrypts a premaster secret with the server’s RSA public key and sends it over. The server decrypts it with PKCS#1 v1.5 and proceeds. A server that behaves differently when the padding is wrong hands the attacker exactly the signal the attack needs, once per request, as often as they care to ask.

    Turning one bit into the whole plaintext

    Here is the idea without the full bookkeeping. Define B = 2^(8*(k-2)), which is the place value of that second byte. A conforming message, one that begins with 00 02, always sits in a known range:

    2B <= m < 3B

    The attacker picks a multiplier s, builds c' = c * s^e mod N, and submits it. When the oracle says conforming, they have learned that m * s mod N also sits between 2B and 3B. That single inequality rules out a slice of possible values for m. The attacker keeps choosing new values of s, each conforming answer adds another constraint, and the set of possible plaintexts shrinks. Bleichenbacher’s method chooses those multipliers so the remaining interval roughly halves each round, and it closes in on the one true m.

    The key never leaked and the modulus was never factored. The server simply answered, a million times, one question it believed was harmless: did this decrypt to something that starts with the right two bytes?

    The original attack needed on the order of a million queries, which is why people still call it the million message attack. Later refinements cut the count sharply by picking smarter multipliers, but the shape is unchanged: adaptive queries, one bit of padding feedback each, a hidden plaintext squeezed to a point. For an invented billing service, call it Acme Invoicing, that decrypts an RSA protected token and returns a clean error on bad padding, a patient loop recovers the token contents. This is the same structural flaw as a padding oracle attack on CBC, moved to the asymmetric side: a formatting check on attacker controlled bytes whose result is allowed to escape.

    The modern resurrection in TLS

    This is not a closed chapter from 1998. In 2016 and 2017 researchers found that many live TLS servers still carried a bleichenbacher attack oracle in their RSA key exchange. One line of work abused the ancient SSLv2 protocol as a decryption oracle against recorded modern traffic that shared an RSA key. Another, named the Return Of Bleichenbacher’s Oracle Threat, showed that a long list of mainstream implementations leaked the conforming signal through small differences in how they handled malformed key exchange records, such as different alert messages on padding failure versus other failures. The lesson repeated each time: an oracle does not need an explicit error string. A status code, an alert type, or a timing gap is the same bit by another name.

    The fix: remove the padding, make responses uniform

    The clean fix is to stop using RSA PKCS#1 v1.5 for key transport at all. Modern TLS prefers forward secret key exchange with ephemeral Diffie Hellman, and TLS 1.3 removed RSA key transport from the protocol entirely, which deletes the oracle’s target outright. If you are encrypting data with RSA directly, use RSA OAEP, a padding scheme designed so that malformed ciphertext cannot be distinguished in a way that helps an attacker.

    Where legacy RSA key exchange cannot be removed yet, the defense is to make the response identical whether padding is valid or not. The standard countermeasure generates a random premaster secret before looking at the padding, and on any padding failure continues the handshake with that random value. The handshake then fails later at MAC verification in exactly the same way a wrong key would, so the attacker sees one uniform failure and gets no bit back. This decision must not branch on the padding result in any way the attacker can measure, timing included.

    • Drop RSA PKCS#1 v1.5 key exchange in favor of ephemeral Diffie Hellman, and adopt TLS 1.3.
    • Use RSA OAEP, never v1.5, for any new RSA encryption.
    • If v1.5 decryption must stay, respond uniformly, generating a random secret on failure and branching on nothing the attacker can measure.
    • Treat every error, alert, status code, and timing difference as attacker visible.

    For the wider family this belongs to, see our hub on cryptographic oracle attacks, and for how a close cousin abuses loose verification rather than decryption, read about rsa signature forgery. If you are new to how these classes are grouped, start with vulnerability basics.

    The assumption that breaks

    Strip away the modular arithmetic and one assumption holds the whole thing up. The server assumes that telling a client whether a message was formatted correctly gives nothing away. It feels harmless, the way a clear error for a malformed request feels like good manners. But that single bit, asked enough times against ciphertext the attacker multiplies at will, is a full decryption oracle. You find this by asking, of every response a system gives, what it tells someone asking ten thousand times on purpose. That is what an autonomous researcher built to test assumptions does: reason about how an app uses a primitive and whether a secret dependent response leaks. Read more on our about page.

    Frequently asked questions

    What is a Bleichenbacher attack in simple terms?

    It is a way to recover the plaintext behind an RSA ciphertext without the private key. The attacker sends many altered ciphertexts and watches whether the server says each one decrypted to a message with valid PKCS#1 v1.5 padding. Each yes or no answer narrows the hidden message, and after enough queries the plaintext is fully recovered. The key is never factored; only the padding check leaks.

    Why does multiplying the ciphertext help the attacker?

    RSA is multiplicative. If you multiply a ciphertext by a chosen value raised to the public exponent, the decrypted plaintext is multiplied by that same value, shown by (c * s^e)^d = m * s mod N. The attacker cannot read the plaintext, but they can steer it by choosing the multiplier, then ask the padding oracle what the steered result looked like.

    Is the Bleichenbacher attack still relevant in modern TLS?

    Yes. In 2016 and 2017 researchers found that many live TLS servers still carried the same oracle in their RSA key exchange, leaking the padding signal through small differences in alerts or timing. TLS 1.3 removed RSA key transport entirely, which is the cleanest defense. The MITRE entry CWE-208, observable timing discrepancy covers one way the signal escapes.

    How do you prevent a Bleichenbacher attack?

    Stop using RSA PKCS#1 v1.5 for key transport and move to ephemeral Diffie Hellman and TLS 1.3, which deletes the target. For direct RSA encryption use RSA OAEP instead of v1.5. If v1.5 decryption must stay, make the response uniform by generating a random secret on padding failure and continuing, so the attacker sees one identical failure no matter what and gets no bit back.


    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.

  • Cryptographic Oracle Attacks: One Bit at a Time

    Cryptographic Oracle Attacks: One Bit at a Time

    Most people imagine a broken cipher when they think about cryptography failing. The real story is usually quieter. Cryptographic oracle attacks do not break the math. They win by asking a system the same small question over and over and reading one bit of the answer each time, until those bits add up to a secret. An oracle here is any behavior that tells an attacker something true about secret data: a padding check that passes or fails, an error that differs from a timeout, a reply that comes back slightly faster. This post defines the class, names the main sub families, and points to a deeper teardown of each.

    What cryptographic oracle attacks are

    An oracle is a source of answers. The attacker wants answers about a key, a plaintext, or a password, and the system is built to keep those private. An oracle leaks them by accident. The attacker never sees the secret directly. They see how the system reacts to inputs they control, and that reaction depends on the secret.

    Every oracle attack has the same shape once you strip it down:

    • The attacker sends a crafted input, often a modified ciphertext or a guessed value.
    • The system processes it and reacts in a way the attacker can observe, such as accept or reject, fast or slow, one error message or another.
    • That observable reaction depends on a secret.
    • Each query rules out some candidates. Repeat, and the set shrinks until one value is left.

    The part that makes these attacks sneaky is that no single query looks dangerous. Each request is well formed and each reply leaks only a fraction of a bit. The damage is in the total. A server answering a million probes politely has no idea it is reading a secret out loud.

    An oracle turns a secret into a guessing game the attacker is allowed to win, one yes or no answer at a time.

    The main sub families of cryptographic oracle attacks

    Oracles differ by which signal leaks and what the attacker learns from it. Four families cover most of what you will meet.

    Padding oracles

    A decryptor checks whether a decrypted message has valid padding and reacts differently when it does not. That single accept or reject bit is enough to recover plaintext byte by byte, with no key at all. It works against the common mode where a cipher processes fixed size blocks and pads the last one. The classic write up is our padding oracle attack teardown. A close relative lets an attacker flip bits in ciphertext to change decrypted plaintext in a chosen way, covered in CBC bit flipping.

    Timing oracles

    Sometimes the leak is not an error but a clock. If the time a server takes to answer depends on a secret, the duration is the oracle. A comparison that stops at the first wrong byte leaks how many leading bytes were correct. A decrypt that does more work on valid padding than on invalid padding leaks the same bit a padding oracle does, just through timing. See constant time comparison for the defensive side and Lucky Thirteen for a timing oracle hidden inside a padding check.

    Partitioning oracles

    Here the attacker crafts one ciphertext that decrypts validly under a large set of keys or passwords at once. A single accept or reject answer then rules out that whole set, not one candidate. This turns a slow password guessing loop into a fast binary search. Read the full mechanism in the partitioning oracle attack, which depends on a property explained in the key commitment attack.

    Compression oracles

    When a system compresses data before encrypting it, the length of the output depends on how repetitive the input was. If an attacker can mix their own guess with a secret in the same compressed buffer, a shorter ciphertext means the guess matched part of the secret. Length becomes the oracle. This is the class behind the named CRIME and BREACH attacks, broken down in the compression oracle attack.

    Why they slip past review

    Oracle bugs hide because every piece looks correct on its own. The cipher is a standard library call. The padding check is the one the textbook describes. The error handling returns a clear message, which feels like good engineering. The flaw only appears when you ask a different question: does anything the system does, including how long it takes and which error it returns, change based on a secret? That question is about behavior, not about any single line of code, which is why static pattern checks tend to miss it.

    For a grounding in how these fit the wider bug landscape, see vulnerability basics.

    How to close an oracle

    The defenses share a theme: make the observable behavior independent of the secret.

    • Authenticate before you decrypt. If a bad ciphertext is rejected by a MAC check before any padding or parsing runs, there is no padding bit to leak. Verify the MAC first.
    • Return one error, always. Do not tell the caller whether padding, parsing, or authentication failed. One generic failure, one code path.
    • Compare in constant time. Secret comparisons must take the same time no matter where the first mismatch sits.
    • Use committing encryption when passwords are involved. A key committing scheme makes a ciphertext valid under exactly one key, which removes the partitioning oracle.
    • Do not compress secret and attacker data together. Separate the contexts, or skip compression for sensitive responses.
    • Rate limit and alert. Oracle attacks need many queries. Caps and anomaly alerts raise the cost and shorten the window.

    The common root

    Across all four families the cipher is fine. The leak lives in the code around it: an error branch, a timing gap, a length, a check that accepts too many keys. Finding these means reasoning about how an application uses a primitive and whether any secret dependent behavior escapes, rather than matching a list of known bad calls. That assumption testing is what an autonomous researcher like UnboundCompute is built to do. Read how we think about it on our about page.

    Frequently asked questions

    What is a cryptographic oracle?

    A cryptographic oracle is any behavior of a system that reveals one true fact about secret data. It might be a padding check that passes or fails, an error that differs from a timeout, or a reply that returns faster on a correct guess. The attacker sends crafted inputs, reads how the system reacts, and each reaction depends on a secret, so repeated queries recover it.

    Do cryptographic oracle attacks break the encryption algorithm?

    No. In almost every case the cipher is sound and the key is never broken. The leak lives in the code around the cipher, such as an error branch, a timing gap, a response length, or a check that accepts too many keys. That is why these bugs survive code review: each line looks correct on its own.

    What are the main types of cryptographic oracle attacks?

    Four families cover most cases. Padding oracles leak an accept or reject bit from a padding check. Timing oracles leak through how long a response takes. Partitioning oracles use one ciphertext valid under many keys to rule out large sets per query. Compression oracles leak a secret through ciphertext length when data is compressed then encrypted.

    How do you defend against oracle attacks?

    Make every observable behavior independent of the secret. Verify a MAC before decrypting, return one generic error for every failure, compare secrets in constant time, use committing encryption when keys are guessable, keep secrets out of shared compression contexts, and rate limit queries so an attacker cannot send the volume these attacks need.


    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.