# `decrypt/oaep` ## Purpose RSAES-OAEP decryption per RFC 8017 §7.1.2 — counterpart to `encrypt/oaep`. Modern default for RSA public-key encryption. The defining concern of this module is the **Manger defence**: every padding-format failure surfaces the SAME `CRYPTO_RSA.DECRYPT_FAILED` error so a remote attacker cannot mount the chosen-ciphertext attack Manger described against naive OAEP implementations in 2001. ## Features - Combined error path — leading-byte mismatch, `lHash` mismatch (wrong label), missing `0x01` separator, non-zero byte in `PS` before separator, integer ≥ `n`, ciphertext length ≠ `k`, modulus too small for the chosen hash: every failure raises the single `DECRYPT_FAILED` code. - Constant-time `lHash` comparison via `constantTimeEqual` from `@suaveplan/crypto-utils` (the comparison we DO have a guarantee about — the rest of the unwrap is best-effort). - RSA blinding wired in by default; `{ blinding: false }` opt-out emits the `crypto_rsa.blinding_disabled_total{operation="decrypt"}` counter. - CRT-accelerated private operation. - `crypto_rsa.decrypt_duration_ms{scheme="oaep",hash}` histogram. ## Usage: Basic Example ```ts import { rsaDecryptOaep } from "@suaveplan/crypto-rsa/decrypt/oaep"; const plaintext = rsaDecryptOaep(privateKey, ciphertext, { hash: "SHA-256", }); ``` ## Usage: Advanced Example OAEP-SHA-384 with explicit label — the matching encrypt MUST have used the same hash, MGF hash, AND label or this throws `DECRYPT_FAILED`: ```ts import { rsaDecryptOaep } from "@suaveplan/crypto-rsa/decrypt/oaep"; const plaintext = rsaDecryptOaep(privateKey, ciphertext, { hash: "SHA-384", mgfHash: "SHA-384", label: new TextEncoder().encode("session-2026-05-11"), allowLegacy: false, }); ``` ## API Reference ### `rsaDecryptOaep(key, ciphertext, options?): Uint8Array` - `key: RsaPrivateKey` - `ciphertext: Uint8Array` — must equal `k = ceil(bitLength / 8)`. - `options.hash?: RsaHashAlgorithm` — defaults to `"SHA-256"`. - `options.mgfHash?: RsaHashAlgorithm` — defaults to `hash`. - `options.label?: Uint8Array` — defaults to empty. - `options.rng?: Rng` — randomness source for blinding. - `options.blinding?: boolean` — defaults to `true`. - `options.allowLegacy?: boolean` — accept 1024-bit moduli. - Returns the recovered plaintext. - Throws `CRYPTO_RSA.DECRYPT_FAILED` on every padding failure. ## Implementation Notes - The combined error path is enforced by routing every failure through the private `decryptFailed()` helper — a single callsite makes future drift detectable in code review. - `constantTimeEqual` from `@suaveplan/crypto-utils` covers the `lHash` byte comparison; the surrounding unwrap loop is NOT constant-time because JavaScript bigint and `Uint8Array.slice` are not. This is the same practical limitation as PKCS1-v1_5 decryption — for FIPS-grade side-channel resistance, defer to `crypto-browser` (WebCrypto) or `crypto-sodium`. - The OAEP separator scan walks `db` from index `hLen` onward and rejects on either a non-zero, non-0x01 byte before the separator or no separator at all. This implements RFC 8017 §7.1.2 step 3.g exactly.