Bouncy Castle Java releases 1.60 and FIPS 1.0.1 (and former) have precisely the issue exploited in Manger's attack: an exception occurs when a ciphertext $c$ is submitted such that $c^d\bmod N$, expressed as a bytetring as wide as $N$, does not start with a 0x00
byte; and then the rest of the decryption process does not occur, leading to markedly faster execution. If an adversary can iteratively submit a few thousands messages for decryption and detect the exception or the faster execution, s/he can decipher a ciphertext, or make a signature. The private key itself does not leak.
Pre-release version 1.61b03 (available here, also on GitHub) has a fix that removes the unwanted exception, and reduces timing variation by orders of magnitude. If it remains vulnerable, that could only be with precise and repeated timing by an attacker.
Manger's attack and countermeasures in later editions of PKCS#1v2
The attack is exposed in James Manger's A Chosen Ciphertext Attack on RSA Optimal Asymmetric Encryption Padding (OAEP) as Standardized in PKCS #1 v2.0 (in proceedings of Crypto 2001). It exploits an RSA decryption system that leaks a little information about $y=x^d\bmod N$ for $x$ repeatedly chosen by an attacker.
The applied part of the attack focuses on the leak of if $y<2^{8k-8}$ with $N$ of $8k-7$ to $8k$ bit; or equivalently: does $y$ start with a 0x00
byte when expressed as a big-endian bytestring of $k$ bytes, as it should in RSAES-OAEP padding:
In PKCS#1 v2 7.1.2 (Decryption operation) step 4, that 0x00
is verified thru the use of "length $k-1$" in:
Convert the message representative $m$ to an encoded message $\text{EM}$ of length $k-1$ octets: $\text{EM}=\text{I2OSP}(m,k-1)$
If I2OSP outputs "integer too large," then output "decryption error" and stop.
PKCS#1v2 has a note stating that the above "decryption error" must be identical to one that can occur on failure of another redundancy check made after OAEP's masking is undone.
Manger observed that not all implementations do this properly; and those that do as instructed take less time when that test fails (due to the stop, the rest of the rest of the procedure is not performed, saving time), thus timing can reveal the desired bit of information.
This is addressed in PKCS#1 v2.1 (and PKCS#1 v2.2, identical in this regard) by using $\text{I2OSP}(m,k)$ without the $-1$, which outputs an extra additional byte $Y$ and never triggers an error. $Y$ is tested in the end, together with other redundancy criteria applying to the message after OAEP unmasking.
If there is no octet with hexadecimal value 0x01
to separate $\text{PS}$ from $M$, if $\text{lHash}$ does not equal $\text{lHash’}$, or if $Y$ is nonzero, output "decryption error" and stop.
Note. Care must be taken to ensure that an opponent cannot distinguish the different error conditions in (the above), whether by error message or timing, or, more generally, learn partial information about the encoded message EM. Otherwise an opponent may be able to obtain useful information about the decryption of the ciphertext, leading to a chosen-ciphertext attack such as the one observed by Manger.
All versions of RSAES-OAEP in PKCS#1v2.0, 2.1 and 2.2 are interoperable, and the procedures described lead to identical results, except that their timing variation is less likely to leak information in 2.1 and later.
Bouncy Castle's code
In Bouncy Castle's version 1.60 (current when the question was asked and this answer first drafted) the code specific to RSAES-OAPEP decryption is decodeBlock
in OAEPEncoding.java
. That contains
byte[] data = engine.processBlock(in, inOff, inLen);
byte[] block = new byte[engine.getOutputBlockSize()];
// as we may have zeros in our leading bytes for the block we produced
// on encryption, we need to make sure our decrypted block comes back
// the same size.
System.arraycopy(data, 0, block, block.length - data.length, data.length);
The definition of engine
is such that processBlock
returns data
with leading zero bytes suppressed, and getOutputBlockSize
returns $k-1$ when $N$ is $k$-byte. It follows that block.length - data.length
is $-1$ when $(c^d\bmod N)\ge2^{8k-8}$, and then System.arraycopy
makes an ArrayIndexOutOfBoundsException not locally caught nor listed in those that the caller is invited to expect, and that the rest of the function is not executed.
RSA/ECB/OAEPWithSHA-256AndMGF1Padding
and then you will know what is really used (Java by default uses pkcs1.5 when not specified, I am not sure for BC) $\endgroup$new String
orgetBytes
without explicitly mentioning the character set is a known source of encoding bugs as it uses the platform default. I'll not talk about the exception handling; I presume the code is for showing us the problem at hand. $\endgroup$