Files
suaveplan/packages/security-stack/crypto/crypto-rsa/src/encoding/emsa-pkcs1-v1_5.md
2026-05-27 23:47:53 +01:00

3.6 KiB

emsa-pkcs1-v1_5

Purpose

Implements EMSA-PKCS1-v1_5-ENCODE from RFC 8017 §9.2 — the encoding method underlying RSASSA-PKCS1-v1_5 signatures. This module also pins the four DigestInfo prefix byte sequences from §9.2 note 1 as named constants so consumers and tests can audit the bytes against the RFC.

Features

  • Hard-coded DIGEST_INFO_PREFIX_* constants for SHA-1, SHA-256, SHA-384, SHA-512
  • digestInfoPrefix(hash) lookup — throws CRYPTO_RSA.UNSUPPORTED_HASH on unknown algorithm
  • emsaPkcs1v15Encode(message, emLen, hash) — RFC 8017 §9.2 conformant encoder
  • Detects modulus-too-short conditions (emLen < tLen + 11) and throws CRYPTO_RSA.INVALID_KEY
  • Returns a fresh Uint8Array of length exactly emLen

Usage: Basic Example

import { emsaPkcs1v15Encode } from "@suaveplan/crypto-rsa/encoding/emsa-pkcs1-v1_5";

const message = new TextEncoder().encode("hello");
const em = emsaPkcs1v15Encode(message, 256, "SHA-256");
console.log(em.length); // 256
console.log(em[0], em[1]); // 0x00, 0x01

Usage: Advanced Example

The encoded message is the input to the RSA private-key operation in rsaSignPkcs1v15. To verify a signature, the same encoder is run on the candidate message and the result is constant-time compared against the public-key recovery of the signature.

import { emsaPkcs1v15Encode, digestInfoPrefix } from "@suaveplan/crypto-rsa/encoding/emsa-pkcs1-v1_5";
import { toHex } from "@suaveplan/crypto-utils";

const prefix = digestInfoPrefix("SHA-256");
console.log(toHex(prefix as Uint8Array));
// 3031300d060960864801650304020105000420

const em1 = emsaPkcs1v15Encode(new Uint8Array([1, 2, 3]), 256, "SHA-256");
const em2 = emsaPkcs1v15Encode(new Uint8Array([1, 2, 3]), 256, "SHA-256");
// em1 and em2 are byte-identical (encoding is deterministic)

API Reference

emsaPkcs1v15Encode(message, emLen, hash): Uint8Array

  • message: Uint8Array — the bytes that will be digested
  • emLen: number — the target encoded-message length, equal to byteLength(n)
  • hash: RsaHashAlgorithm — "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512"
  • returns Uint8Array of length exactly emLen
  • throws CryptoRsaError(CRYPTO_RSA.INVALID_KEY) when emLen < tLen + 11 (modulus too short for the chosen hash)
  • throws CryptoRsaError(CRYPTO_RSA.UNSUPPORTED_HASH) on unknown algorithm

digestInfoPrefix(hash): Readonly<Uint8Array>

Returns the immutable RFC 8017 §9.2 note 1 DigestInfo prefix for hash.

DIGEST_INFO_PREFIX_SHA1 / _SHA256 / _SHA384 / _SHA512

Read-only Uint8Array constants holding the byte-exact DigestInfo prefix sequences from RFC 8017 §9.2 note 1.

Implementation Notes

  • The encoded message structure is 0x00 || 0x01 || PS || 0x00 || T, where PS is at least 8 bytes of 0xff (per RFC 8017 §9.2 step 3). The tLen + 11 floor encodes the 2-byte header (0x00 || 0x01), the 1-byte separator (0x00), and the 8-byte minimum for PS.
  • The DigestInfo prefix bytes are pinned as literal byte arrays — not ASN.1 encoder calls — so a single misbyte in the RFC table would be caught by the bytewise prefix tests against the RFC reference.
  • The emLen parameter is supplied by the caller (= byteLength(n) for RSASSA-PKCS1-v1_5). This module does not compute emLen from a key; that responsibility belongs to the higher-level signature scheme.
  • Encoding is deterministic — the same (message, emLen, hash) triple always produces the same EM bytes.
  • Hash dispatch goes through digestWith from mgf1.ts, so SHA-2 flows through @suaveplan/crypto-hash and SHA-1 through Noble.