Files
skills/docs/security-stack/crypto/crypto-rsa/src/decrypt/pkcs1v15.md
2026-05-29 08:20:56 +01:00

3.4 KiB
Raw Blame History

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

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 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".