first commit
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# `decrypt/pkcs1v15`
|
||||
|
||||
## Purpose
|
||||
|
||||
RSAES-PKCS1-v1_5 decryption per RFC 8017 §7.2.2. Counterpart to
|
||||
`encrypt/pkcs1v15`. Provides legacy interop for protocols that
|
||||
predate OAEP (TLS 1.0–1.2 RSA key transport, JWE `RSA1_5` legacy
|
||||
mode). Modern callers MUST prefer `decrypt/oaep`.
|
||||
|
||||
The defining concern of this module is the **Bleichenbacher
|
||||
defence**: every padding-format failure surfaces the SAME
|
||||
`CRYPTO_RSA.DECRYPT_FAILED` error so a remote attacker observing
|
||||
the typed-error code (or the OTel error counter) cannot mount a
|
||||
million-message chosen-ciphertext attack.
|
||||
|
||||
## Features
|
||||
|
||||
- Combined error path — wrong leading byte, wrong block-type
|
||||
marker, missing `0x00` separator, `PS` shorter than 8 bytes,
|
||||
ciphertext length ≠ `k`, post-decrypt integer ≥ `n`: every
|
||||
failure throws the single `DECRYPT_FAILED` code.
|
||||
- RSA blinding wired in by default — `{ blinding: false }` opt-out
|
||||
emits the `crypto_rsa.blinding_disabled_total{operation="decrypt"}`
|
||||
counter so production dashboards can flag any caller that
|
||||
disables the timing-side-channel defence.
|
||||
- CRT-accelerated private operation (`rsaPrivateOperationCrt`).
|
||||
- Modulus-length policy gate runs first.
|
||||
- `crypto_rsa.decrypt_duration_ms{scheme="pkcs1v15"}` histogram.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { rsaDecryptPkcs1v15 } from "@suaveplan/crypto-rsa/decrypt/pkcs1v15";
|
||||
import type { RsaPrivateKey } from "@suaveplan/crypto-rsa";
|
||||
|
||||
declare const privateKey: RsaPrivateKey;
|
||||
declare const ciphertext: Uint8Array;
|
||||
const plaintext = rsaDecryptPkcs1v15(privateKey, ciphertext, {
|
||||
allowLegacy: false,
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
Disable blinding (NOT recommended outside benchmarks) — the
|
||||
counter increment surfaces the choice in production telemetry:
|
||||
|
||||
```ts
|
||||
import { rsaDecryptPkcs1v15 } from "@suaveplan/crypto-rsa/decrypt/pkcs1v15";
|
||||
|
||||
const plaintext = rsaDecryptPkcs1v15(privateKey, ciphertext, {
|
||||
blinding: false, // emits crypto_rsa.blinding_disabled_total{operation="decrypt"}
|
||||
allowLegacy: true,
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `rsaDecryptPkcs1v15(key, ciphertext, options?): Uint8Array`
|
||||
|
||||
- `key: RsaPrivateKey` — modulus must satisfy the policy gate.
|
||||
- `ciphertext: Uint8Array` — must be exactly `k` bytes; any
|
||||
other length throws `DECRYPT_FAILED`.
|
||||
- `options.rng?: Rng` — randomness source for blinding.
|
||||
- `options.blinding?: boolean` — defaults to `true`. Set to
|
||||
`false` only for benchmark comparisons.
|
||||
- `options.allowLegacy?: boolean` — accept 1024-bit moduli.
|
||||
- Returns the recovered plaintext (`em.length - separator - 1`
|
||||
bytes).
|
||||
- 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 for `new CryptoRsaError(DECRYPT_FAILED, ...)` makes
|
||||
it impossible for a future edit to accidentally introduce a
|
||||
differentiated error.
|
||||
- True constant-time decryption is not achievable in JavaScript
|
||||
without WebCrypto (V8/JSC bigint ops are not constant-time).
|
||||
We mitigate with blinding and with eager-throw bucketing all
|
||||
padding failures under one error class. For FIPS-grade
|
||||
side-channel resistance, route through `crypto-browser`
|
||||
(WebCrypto) or `crypto-sodium`.
|
||||
- The `c >= key.n` check is RFC 8017 §5.1.2's "ciphertext
|
||||
representative out of range" condition — also bucketed under
|
||||
`DECRYPT_FAILED` so callers cannot distinguish "padding bad"
|
||||
from "ciphertext outside `Z*_n`".
|
||||
Reference in New Issue
Block a user