# `encrypt/oaep` ## Purpose RSAES-OAEP encryption per RFC 8017 §7.1.1. The Optimal Asymmetric Encryption Padding (OAEP) scheme is the modern default for RSA public-key encryption — provably secure under the random-oracle model when the underlying hash is collision-resistant, and immune to the chosen-ciphertext attacks (Bleichenbacher, Manger) that plague PKCS1-v1_5. This module provides the encryption side; the matching decrypt lives in `decrypt/oaep` and applies the Manger-safe combined error path. ## Features - RFC 8017 §7.1.1 reference algorithm with caller-overridable hash (`SHA-256` default), MGF hash (defaults to the OAEP hash), and optional label (defaults to empty). - Routes hashing through `@suaveplan/crypto-hash` for SHA-256/384/512 and `@noble/hashes/sha1` for SHA-1; same dispatcher Stage 2's `mgf1` uses, so OAEP and MGF1 stay in lock-step. - Modulus-length policy gate runs first; sub-1024-bit keys rejected outright. - Plaintext oversize → `CRYPTO_RSA.INVALID_KEY` with `{ mLen, max }` context for diagnosability. - `crypto_rsa.encrypt_duration_ms{scheme="oaep",hash}` histogram. ## Usage: Basic Example ```ts import { rsaEncryptOaep } from "@suaveplan/crypto-rsa/encrypt/oaep"; const ciphertext = rsaEncryptOaep( publicKey, new TextEncoder().encode("hello"), { hash: "SHA-256" } ); ``` ## Usage: Advanced Example OAEP with SHA-384 hashing and a non-empty label (mirrors a common JWE `RSA-OAEP-384` deployment with a context-bound label): ```ts import { rsaEncryptOaep } from "@suaveplan/crypto-rsa/encrypt/oaep"; const label = new TextEncoder().encode("session-2026-05-11"); const ct = rsaEncryptOaep(publicKey, plaintext, { hash: "SHA-384", mgfHash: "SHA-384", label, allowLegacy: false, }); ``` ## API Reference ### `rsaEncryptOaep(key, plaintext, options?): Uint8Array` - `key: RsaPublicKey` - `plaintext: Uint8Array` — at most `k - 2*hLen - 2` bytes. - `options.hash?: RsaHashAlgorithm` — defaults to `"SHA-256"`. - `options.mgfHash?: RsaHashAlgorithm` — defaults to `hash`. - `options.label?: Uint8Array` — defaults to empty. - `options.rng?: Rng` — defaults to `defaultRng()`. - `options.allowLegacy?: boolean` — accept 1024-bit moduli. - Throws `CRYPTO_RSA.INVALID_KEY` on plaintext oversize, `CRYPTO_RSA.UNSUPPORTED_HASH` on unknown algorithm, `CRYPTO_RSA.MGF_FAILURE` on absurd mask-length input, modulus-length policy violations on bad key. ## Implementation Notes - The seed is drawn from the injected `Rng` via the shared `nextBytesViaRng` helper (same path as blinding) so seeded test RNGs work transparently. - We share the `mgf1` and `digestWith` implementations from Stage 2's `src/encoding/mgf1.ts` rather than duplicating — RFC 8017 §B.2.1 is short but having two copies invites drift. - `db` is allocated once and built in place; XOR with the mask produces a new buffer rather than mutating in place because `xor` from `@suaveplan/crypto-utils` returns a fresh allocation (length-mismatch guard). - The OAEP encoding allocates `O(k)` — for 4096-bit keys this is 512 bytes. Hot-loop callers (e.g. JWE bulk re-encryption) should prefer keeping a single `RsaPublicKey` and amortising the modpow cost over many ciphertexts.