3.4 KiB
3.4 KiB
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
0x00separator,PSshorter than 8 bytes, ciphertext length ≠k, post-decrypt integer ≥n: every failure throws the singleDECRYPT_FAILEDcode. - RSA blinding wired in by default —
{ blinding: false }opt-out emits thecrypto_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
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:
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 exactlykbytes; any other length throwsDECRYPT_FAILED.options.rng?: Rng— randomness source for blinding.options.blinding?: boolean— defaults totrue. Set tofalseonly for benchmark comparisons.options.allowLegacy?: boolean— accept 1024-bit moduli.- Returns the recovered plaintext (
em.length - separator - 1bytes). - Throws
CRYPTO_RSA.DECRYPT_FAILEDon every padding failure.
Implementation Notes
- The combined error path is enforced by routing every failure
through the private
decryptFailed()helper — a single callsite fornew 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) orcrypto-sodium. - The
c >= key.ncheck is RFC 8017 §5.1.2's "ciphertext representative out of range" condition — also bucketed underDECRYPT_FAILEDso callers cannot distinguish "padding bad" from "ciphertext outsideZ*_n".