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

3.7 KiB

emsa-pss-verify

Purpose

Implements the EMSA-PSS verification operation defined in RFC 8017 §9.1.2. Given the message digest, the candidate encoded message EM, the encoded-message bit length, the expected salt length, and the hash + MGF1 hash pair, this function returns whether EM is a valid PSS encoding of the digest.

Features

  • RFC 8017 §9.1.2 conformant — full structure, padding, and trailer checks
  • Returns false on every malformed input rather than throwing — the signature scheme decides how to escalate
  • Constant-time final compare via constantTimeEqual from @suaveplan/crypto-utils
  • Validates leftmost-bit clearance (RFC 8017 §9.1.2 step 6)
  • Validates the 0xbc trailer
  • Validates the all-zero PS region and the 0x01 separator

Usage: Basic Example

import { emsaPssVerify } from "@suaveplan/crypto-rsa/encoding/emsa-pss-verify";
import { emsaPssEncode } from "@suaveplan/crypto-rsa/encoding/emsa-pss-encode";
import { sha256 } from "@suaveplan/crypto-hash";

const mHash = sha256(new TextEncoder().encode("hello"));
const salt = new Uint8Array(32);
const em = emsaPssEncode(mHash, 2047, salt, "SHA-256", "SHA-256");
console.log(emsaPssVerify(mHash, em, 2047, 32, "SHA-256", "SHA-256")); // true

Usage: Advanced Example

A bit-flipped EM is rejected at one of several gates: the trailer check, the leftmost-bit check, the PS region check, the separator check, or the final salt+hash compare. All of these gates fail closed (return false) without revealing which gate caught the divergence.

import { emsaPssVerify } from "@suaveplan/crypto-rsa/encoding/emsa-pss-verify";
import { emsaPssEncode } from "@suaveplan/crypto-rsa/encoding/emsa-pss-encode";
import { sha256 } from "@suaveplan/crypto-hash";

const mHash = sha256(new TextEncoder().encode("hello"));
const em = emsaPssEncode(
    mHash,
    2047,
    new Uint8Array(32),
    "SHA-256",
    "SHA-256"
);
const tampered = new Uint8Array(em);
tampered[0] = tampered[0] ^ 0x01;
console.log(emsaPssVerify(mHash, tampered, 2047, 32, "SHA-256", "SHA-256"));
// false

API Reference

emsaPssVerify(mHash, em, emBits, saltLength, hash, mgfHash): boolean

  • mHash: Uint8Array — Hash(message); length must equal hashOutputLength(hash) (otherwise returns false)
  • em: Uint8Array — candidate encoded message; length must equal ceil(emBits / 8) (otherwise returns false)
  • emBits: number — encoded-message bit length, typically modulusBits - 1
  • saltLength: number — expected salt length, must match the value used by emsaPssEncode
  • hash: RsaHashAlgorithm — message hash; also used to digest M'
  • mgfHash: RsaHashAlgorithm — MGF1 hash for dbMask
  • returns true when EM is a valid PSS encoding of mHash under the triple, false otherwise

Implementation Notes

  • The function never throws on encoding-level failures — every malformed input returns false. This matches the RFC 8017 §9.1.2 contract: the caller is responsible for distinguishing between expected verify failures and protocol errors.
  • The final H == H' comparison is constant-time. The earlier structural checks (trailer byte, PS region, separator byte) short- circuit on first failure — that is acceptable because the public inputs determining those bytes are known to the verifier; only the hash output carries the secret-derived signal that timing attacks would target.
  • The leftmostBitsToClear check (RFC 8017 §9.1.2 step 6) prevents a trivial signature-malleability attack where an attacker flips bits in the cleared region without being detected.
  • Returning false on a length mismatch (em.length !== emLen or mHash.length !== hLen) preserves the same constant-effort failure shape as a structurally-valid mismatch.