3.1 KiB
3.1 KiB
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,
lHashmismatch (wrong label), missing0x01separator, non-zero byte inPSbefore separator, integer ≥n, ciphertext length ≠k, modulus too small for the chosen hash: every failure raises the singleDECRYPT_FAILEDcode. - Constant-time
lHashcomparison viaconstantTimeEqualfrom@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 thecrypto_rsa.blinding_disabled_total{operation="decrypt"}counter. - CRT-accelerated private operation.
crypto_rsa.decrypt_duration_ms{scheme="oaep",hash}histogram.
Usage: Basic Example
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:
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: RsaPrivateKeyciphertext: Uint8Array— must equalk = ceil(bitLength / 8).options.hash?: RsaHashAlgorithm— defaults to"SHA-256".options.mgfHash?: RsaHashAlgorithm— defaults tohash.options.label?: Uint8Array— defaults to empty.options.rng?: Rng— randomness source for blinding.options.blinding?: boolean— defaults totrue.options.allowLegacy?: boolean— accept 1024-bit moduli.- Returns the recovered plaintext.
- Throws
CRYPTO_RSA.DECRYPT_FAILEDon 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. constantTimeEqualfrom@suaveplan/crypto-utilscovers thelHashbyte comparison; the surrounding unwrap loop is NOT constant-time because JavaScript bigint andUint8Array.sliceare not. This is the same practical limitation as PKCS1-v1_5 decryption — for FIPS-grade side-channel resistance, defer tocrypto-browser(WebCrypto) orcrypto-sodium.- The OAEP separator scan walks
dbfrom indexhLenonward 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.