first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# `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.