The RSA cryptosystem has had its fair share of attacks over the years, but among the most impressive, you can find the infamous Bleichenbacher attack [Ble98], which doomed PKCS v1.5 in 1998. Nineteen years later, the ROBOT attack proved that the Bleichenbacher attack was still a concern today. Now, what alternatives to RSA PKCS v1.5 do we have? Well, its successor, RSA OAEP also known as RSA PKCS v2.1 is obviously a good candidate.

RSA OAEP is an interesting scheme because it has been mathematically proven to be secure against a chosen-ciphertext attack in the random oracle model.

Guess what? An attack against “weak implementations” of RSA OAEP also exists. This attack, while less well known than Bleichenbacher’s because it never makes the headlines, is known as “Manger’s attack” [Man01] after the name of its creator, James Manger, who published it in 2001.

In this blog post, I will first explain each part of the attack and then guide you through their implementation in Go. We will end up with a fully functional attack just waiting for a so-called “oracle” to be plugged in. Typical oracles, in practice, are provided by timing information, but sometimes an error message eases the job.

First, let me first introduce the notion of oracle as I’ll use it in this post:

Definition: oracle

An oracle is a black-box that will take an input and answer a specific, fixed question regarding its input, without being restricted in terms of knowledge. It can be used by any user indifferently, including any adversary.

In cryptography, the most common sorts of oracles used to attack different schemes are padding oracles and timing oracles.

The padding oracles, as in Bleichenbacher’s attack, take ciphertexts as input and reveal whether or not the padding is correct after their decryption.

The timing oracles, as used by Kocher [Koc96] for example, usually report the time needed to decrypt a specific ciphertext.

In the end, timing oracles can often be used as padding oracles, typically when the implementation returns an early error if the padding is wrong, leading an attacker to build a padding oracle out of the return timings of an implementation.

Manger’s Attack

Let be an RSA modulus, with and its public and private exponents, respectively, and be its byte length.

Then as long as the attacker has access to an oracle able to tell them whether is less than or not, then they are able to narrow down the range of possible messages corresponding to the ciphertext to only a single message, , in queries.

Furthermore, the attack crucially relies on a well-known property of the RSA cryptosystem, namely its malleability [DDN91]:

Definition: malleability

For an encryption of the plaintext , the scheme is malleable if it is possible for a given function to generate another ciphertext which yields the plaintext , for a function without requiring knowledge of at any point.

The property of being malleable is called malleability.

This is the case for the RSA cryptosystem, since we operate in the quotient ring . For a given public key , the plaintext is encrypted as . We can construct the ciphertext for any , as being .

Now let me formally describe the attack:

Initially we only know one ciphertext, , we would like to decrypt without access to the private key, but with access to an oracle able to tell us, when we send it a value , whether the decrypted value satisfies or not.

I’ll now describe the attack as presented in [Man01] when , which is usually the case since the size of most RSA moduli is an exact multiples of bits, rendering at least times bigger than .

If this is not the case, then the attack is still possible but has to deal with multiple intervals, cf. [Man01] section 3.2.

So, when that assumption holds, the attack is performed in three steps as follows:

Step 1

We firstly have to try to multiply the unknown plaintext with



by sending the values to the oracle for until it returns that . This is true as soon as thanks to the malleability of the RSA cryptosystem.

This ensures us that , so we know that .

This can easily be done in Go as follows:

f1 := new(big.Int).SetInt64(int64(2)) for !tryOracle(f1, c, e, N, ourOracle) { f1.Mul(two, f1) }

Where the tryOracle(f, c, e, N, ourOracle) function is sending the ciphertext to our oracle which returns, for whether or not.

Step 2

We now try to multiply the unknown plaintext with



by exploiting the malleability of the RSA cryptosystem.

To do so, we send to the oracle, for , until it returns that .

It then necessarily implies that the modulo reduction “wrapped” into something smaller than , since by definition of and , we know that .

In turn, it then implies that .

Note that this step necessarily terminates, taking at most oracle queries, since will always be exceeded when .

This is also easily implemented in Go:

nB := new(big.Int).Add(N, B) nBB := new(big.Int).Div(nB, B) f2 := new(big.Int).Mul(nBB, f12) for tryOracle(f2, c, e, N) { f2.Add(f2, f12) }

Step 3

We finally want to narrow down the range of possible messages to just one.

This is done iteratively, approximately dividing the range by two at each step. It uses a heuristic approach to define its parameters and has not been formally proven by Manger in his article.

This implies defining and and then as long as the range is containing more than one value, one can do the following:

Choose a temporary multiple ; Compute a boundary point ; Compute a value , which is so that is spanning a single boundary point at , as follows: ; Send the value to the oracle, if it returns , then one can set and go back to 1. Else, if it returns , once can set and go back to 1.

This is the longest part of the computation and can be implemented as follows in Go (using notations as close to Manger’s article as possible):

mmin := divCeil(N, f2) mmax := new(big.Int).Div(nB, f2) BB := new(big.Int).Mul(two, B) diff := new(big.Int).Sub(mmax, mmin) for diff.Sub(mmax, mmin).Cmp(zero) > 0 { ftmp := new(big.Int).Div(BB, diff) ftmpmmin := new(big.Int).Mul(ftmp, mmin) i := new(big.Int).Div(ftmpmmin, N) iN := new(big.Int).Mul(i, N) f3 := divCeil(iN, mmin) iNB := new(big.Int).Add(iN, B) if tryOracle(f3, c, e, N) { mmin = divCeil(iNB, f3) } else { mmax.Div(iNB, f3) } }

When this iterating process terminates, the range of possible messages only spans one value, the secret message. On average, the attack requires approximately queries, for the bit-size of the RSA modulus.

The code

My complete, working, heavily commented implementation of Manger’s attack in Go can be found on Github and is compatible with generic oracles!

Note that in this implementation, I provide a test file attack_test.go where I am crafting the oracle to tell you whether the most significant byte of the decrypted, padded data is zero or not. This is equivalent to testing whether the decrypted value satisfies or not, by definition of and . This allows me to attack a modified version of Go’s crypto library (to be found in the package moddedrsa ). This file is a black-box test and it is probably a good example (excepted for the infamous dot import) if you want to implement the attack using your own oracle.

In the end, in order to use it with your very own oracle, you just need to implement the “Oracle” interface in your own Go program and call the attack using your oracle.

Let me know if you used this code, I’m curious about the oracles you are using!

Bibliography