Initial Commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# `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.
|
||||
Reference in New Issue
Block a user