first commit
This commit is contained in:
91
docs/security-stack/crypto/crypto-rsa/src/encrypt/oaep.md
Normal file
91
docs/security-stack/crypto/crypto-rsa/src/encrypt/oaep.md
Normal 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.
|
||||
Reference in New Issue
Block a user