The Fujisaki-Okamoto Wrapper
August 25, 20265 min readbeginner
Everything so far builds an encryption scheme that resists eavesdroppers. 02-The-KEM-Contract argued that is not enough, because real attackers send ciphertexts of their own and…
Everything so far builds an encryption scheme that resists eavesdroppers. The KEM Contract argued that is not enough, because real attackers send ciphertexts of their own and watch what happens. This note closes that gap.
The transform is due to Fujisaki and Okamoto, in the variant refined by Hofheinz, Hövelmanns and Kiltz in 2017 to have a tight proof in the quantum random oracle model. It contains no lattice mathematics at all.
01.The idea
Two changes to the scheme, and together they eliminate the reaction channel.
Make the encryption randomness a hash of the message. In the inner scheme, encryption took independent randomness . In the wrapper, is derived by hashing . So encryption becomes deterministic given : anybody who knows the message can reproduce the exact ciphertext.
Verify by re-encrypting. On decapsulation, after recovering , Bob derives from it the same way and re-runs the entire encryption. If the ciphertext he produces matches the one he received, it was honestly generated. If not, something was tampered with.
The second step is what removes the attacker's leverage. A modified ciphertext will not re-encrypt to itself, so it is detected regardless of whether its noise happened to stay within the decryption bound.
02.The algorithms
KeyGen. Run the inner key generation and additionally draw a 32-byte rejection seed . Output
where is SHA3-256. Storing inside is what lets decapsulation re-encrypt without being handed the public key separately.
Encaps().
- Sample a uniform 32-byte .
- Derive , where is SHA3-512 and its 64 bytes split into two halves.
- Compute using the inner scheme.
- Compute .
Output .
Decaps(, ).
- .
- .
- , re-encrypting with the derived randomness.
- If , return . Otherwise return .
03.Implicit rejection
Step 4 of decapsulation is the part worth dwelling on.
The obvious response to a bad ciphertext is to return an error. That is exactly what must not happen. An error is an observable event, and The KEM Contract described how observable events leak the secret one query at a time.
So instead, a mismatch returns a key derived from the rejection seed . That value is pseudorandom, it is 32 bytes like any other key, and it is deterministic in and , so the same bad ciphertext always yields the same wrong key.
From the attacker's side, both paths return 32 indistinguishable bytes. There is no error, no branch to time, and no difference to observe. This is implicit rejection, and it is what closes the family of oracle attacks.
The consequence for the honest protocol is that an attacker who tampers with a ciphertext causes the two parties to derive different keys, so the session simply fails to establish later, at the symmetric layer, with no information having leaked about .
04.What it costs
Decapsulation performs a decryption and then a full encryption. So it is roughly twice the ring arithmetic of the inner scheme, and it is strictly more expensive than encapsulation, which encrypts once.
That is the price of resistance to active attackers, and it is the reason the operation a server performs on every connection is also the most expensive one in the scheme. For hardware acceleration this is the number that matters.
It also forces a discipline on implementations. The comparison in step 4 must be constant-time. A byte-by-byte comparison that returns early on the first mismatch leaks how many leading bytes matched, which reintroduces exactly the channel the whole construction exists to remove. The same applies to the selection between and , which must be a branchless conditional move rather than an if.
05.Where the randomness comes from
Encryption warned that reusing encryption randomness against one public key is a total break. The wrapper is what makes that impossible to get wrong.
Because is derived deterministically from , and is freshly sampled uniformly at random each time, reuse can only happen if the same is drawn twice. With being 32 uniform bytes, that is a 256-bit collision and will not occur.
So the wrapper turns a requirement that implementations could violate, "use fresh randomness every time", into one they cannot, "sample a fresh 32-byte message". The randomness discipline is enforced by construction rather than by documentation.
06.One hash function underneath
The scheme calls several different primitives: SHAKE128 to expand into the matrix, SHAKE256 as the PRF for centred binomial sampling, SHA3-256 for , SHA3-512 for , and SHAKE256 again for the key derivation.
Every one of those is a different parameterisation of the same underlying permutation, Keccak-f[1600]. That is deliberate. A single hardware block implementing Keccak-f serves every hashing role the scheme has, so an accelerator needs one hash engine rather than five.
Chapter 7 builds that permutation.