CryptosignaturesintegrityauthenticityHMACverification

Digital Signatures

Sign with a private key, verify with the public key: proof that a message is unmodified and came from the key holder — the mechanism behind JWTs, webhooks, signed artefacts and passkeys.

▶ Run the labFollow the failure

Frame the problem

Security starts with a concrete asset, attacker capability and trust crossing.

Asset
The authenticity of messages, tokens, artefacts and approvals.
Attacker & capability
Someone forging a message, replaying a valid one, or exploiting a verifier that checks the wrong thing.
Trust boundary
Between a claim and a verifiable claim.
AssetThreatAttack SurfaceTrust BoundaryVulnerabilityExploit PathImpactMitigationDefense in DepthResidual Risk

Sign, then verify

A signature proves integrity (the message is as signed) and authenticity (the signer held the private key). Because anyone can verify with the public key, it also gives non-repudiation — the signer cannot claim someone else produced it — which a MAC cannot, since every MAC verifier could also have created it.

HMAC is the symmetric relative: same integrity and authenticity between parties sharing a secret. Webhooks typically use HMAC because the provider and you share a secret; software artefacts use signatures because many verifiers must not be able to sign.

Message + signature → verification
hashsignMessagePrivate keyPublic keySignatureVerify: valid / invalid
UserLLMAgentToolDataDecisionHumanGuardrail

What a valid signature does not prove

A valid signature proves the bytes were signed by the key. It does not prove they were signed *for you* (audience), *recently* (timestamp/expiry), *once* (nonce/id), or *for this purpose* (context). Every real protocol adds these fields and every real verification must check them — the JWT lessons are entirely about this gap. Verify over the exact bytes received, not a re-serialised form; compare in constant time; and reject before parsing anything else.

1function verifyWebhook(rawBody: Buffer, header: string, secret: string) {
2 const [tsPart, sigPart] = header.split(',')
3 const ts = Number(tsPart.slice(2)); const sig = sigPart.slice(3)
4 if (Math.abs(Date.now() / 1000 - ts) > 300) return false // replay window
5 const expected = hmacSha256(secret, `${ts}.${rawBody}`) // over the exact bytes
6 return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(sig, 'hex'))
7}

Key points

  • Signature = integrity + authenticity + (asymmetric) non-repudiation.
  • HMAC when the parties share a secret; signatures when verifiers must not be able to sign.
  • A valid signature says nothing about audience, freshness, uniqueness or purpose — check those separately.
  • Verify raw bytes, constant-time, before parsing.

Boundary control exercise

This lesson uses the shared boundary-control exercise.

Boundary control check
Untrusted input / identity
Trust boundary
Privileged asset
Prevention may fail silently.

Follow the attack

Safe conceptual simulation: capability → missing control → crossed boundary → asset impact.

  1. 1
    Attacker → replay a validly signed message; or exploit a verifier that skips timestamp/audience; or obtain the signing key.
Blast radius
  • Forged webhooks mark unpaid orders paid; forged tokens grant identity; forged artefacts ship malware.

Defend, detect, recover

One prevention is a single point of security failure. Layer it and make failure observable.

Prevent
  • • Sign with context (audience, timestamp, id); verify all of it; keys in KMS/HSM; rotate.
Detect
  • • Signature failures (near zero normally); replayed ids.
Respond & recover
  • • Rotate keys; identify messages accepted during the window.
Residual risk
  • • Key compromise defeats everything until detected and rotated.