4.0 KiB
4.0 KiB
sign/pkcs1v15
Purpose
RSASSA-PKCS1-v1_5 signature generation per RFC 8017 §8.2.1. Hashes
the message with the configured algorithm, applies the EMSA-PKCS1-v1_5
encoding, and runs the RSA private operation through the CRT path. The
output is a signature of length exactly byteLength(n).
Features
- RFC 8017 §8.2.1 conformant — uses the CRT private-key operation
- Supports SHA-1, SHA-256, SHA-384, SHA-512
- Modulus-length policy gates run on every call (
MODULUS_TOO_SHORT/MODULUS_TOO_LARGE) - Re-uses the canonical
i2osp/os2ipmath primitives - Records
crypto_rsa.sign_duration_ms{scheme,hash,engine}andcrypto_rsa.outcome_total{operation,outcome}on every call - RSA blinding (RFC 8017 §5.1.2 / Kocher 1996) wired by default — opt-out via
{ blinding: false }emitscrypto_rsa.blinding_disabled_total{operation="sign"} - All thrown errors are
CryptoRsaErrorinstances; non-typed exceptions wrap asCRYPTO_RSA.INVALID_SIGNATURE
Usage: Basic Example
import { rsaSignPkcs1v15 } from "@suaveplan/crypto-rsa/sign/pkcs1v15";
const message = new TextEncoder().encode("hello, signed world");
const signature = rsaSignPkcs1v15(privateKey, message, { hash: "SHA-256" });
console.log(signature.length); // === byteLength(n), e.g. 256 for 2048-bit
Usage: Advanced Example
The allowLegacy option lowers the modulus floor from 2048 to 1024 bits
for legacy interop scenarios (e.g. WebAuthn TPM 1.2 endorsement keys).
The allowOversize option lifts the ceiling beyond 16384 bits for
offline tooling. Both default to false.
import { rsaSignPkcs1v15 } from "@suaveplan/crypto-rsa/sign/pkcs1v15";
const sig = rsaSignPkcs1v15(legacy1024PrivateKey, message, {
hash: "SHA-1",
allowLegacy: true,
});
API Reference
rsaSignPkcs1v15(privateKey, message, options): Uint8Array
privateKey: RsaPrivateKey— canonical private key with mandatory CRT parametersmessage: Uint8Array— bytes to be signed; will be hashed internallyoptions.hash: RsaHashAlgorithm— one of"SHA-1","SHA-256","SHA-384","SHA-512"options.allowLegacy?: boolean— see policy docoptions.allowOversize?: boolean— see policy docoptions.blinding?: boolean— defaults totrue; opting out emitscrypto_rsa.blinding_disabled_total{operation="sign"}options.rng?: Rng— randomness source for the blinding factor; defaults todefaultRng()- returns
Uint8Arrayof lengthbyteLength(n) - throws
CryptoRsaError(CRYPTO_RSA.MODULUS_TOO_SHORT)/MODULUS_TOO_LARGE/UNSUPPORTED_HASH/INVALID_KEY
Implementation Notes
- The output equals
i2osp(rsaPrivateOperationCrt(os2ip(em), privateKey), emLen)whereem = emsaPkcs1v15Encode(message, emLen, hash)andemLen = byteLength(n). This is bit-for-bit identical to the naivec = m^d mod nreference, but uses the CRT decomposition for ~3-4× the throughput on a 2048-bit modulus. - Encoding is deterministic — every call with the same
(message, hash)pair produces the same signature. This is RFC 8017 §8.2.1 behaviour: PKCS1-v1_5 is a deterministic signature scheme, in contrast to PSS. - The function never short-circuits on early failure — even when an
exception is raised, the duration histogram and outcome counter are
recorded. The
outcomeattribute distinguishes"ok"from"error". - The engine attribute is
"noble"because the underlying modular exponentiation is@noble/curves/abstract/modular.pow. TheRsaCryptoEngineadapter (./engine) selects"subtle"whenglobalThis.crypto.subtleexposes the matching algorithm. - Blinding — for every call
m' = m * r^e mod nis computed with a freshly sampledr ∈ Z*_n, the CRT private operation runs againstm', and the result is unblinded vias = s' * r^{-1} mod n. This prevents the Kocher 1996 timing-attack class against the secret exponent. Output is bit-for-bit identical to the unblinded path; only the timing varies. The < 5% overhead is bench-verified.