Frontend Labs
⋯

Chapter 21 — Digital signatures

In one sentence

A digital signature uses a private key to produce a tag that anyone with the matching public key can verify, proving the message is unchanged and came from the key's owner — and, unlike a MAC, proving it to third parties.

The problem

HMAC (Chapter 11) proves integrity and authenticity, but both parties share the key, so Bob can't prove to a judge that Alice — not Bob himself — produced a given tag. And HMAC needs a pre-shared secret, which brings back key distribution.

Signatures fix both:

   HMAC                              SIGNATURE
   ----                              ---------
   shared secret key                 private key signs / public key verifies
   either party can produce a tag    only the private-key holder can sign
   verifier could have forged it     verifier demonstrably could NOT have
   -> no non-repudiation             -> NON-REPUDIATION
   needs key pre-shared              public key can be published freely

Non-repudiation — the signer cannot credibly deny signing — is the property that makes signatures the basis of code signing, certificates, contracts, blockchains, and software updates.

How it actually works

You never sign the raw message (it's too big, and raw sign operations have structure). You sign its hash:

   SIGNING                                VERIFYING
   -------                                ---------
   1. h = SHA-256(message)                1. h  = SHA-256(received message)
   2. sig = Sign(private_key, h)          2. h' = Verify(public_key, sig)
   3. send (message, sig)                 3. accept iff h == h'

Consequences of the "sign the hash" design:

   - A broken hash breaks the signature scheme. SHA-1 collisions (Ch 9) meant
     an attacker could get a benign document signed and swap in a malicious
     one with the same hash. This is why CAs abandoned SHA-1.
   - The signature size depends only on the algorithm, not the message.
     A 10 GB file and a 10-byte file get the same 64-byte Ed25519 signature.

The algorithms

Scheme Key type Sig size (approx) Notes
Ed25519 Curve25519 64 bytes First choice. Deterministic, fast, misuse-resistant
ECDSA P-256 NIST P-256 ~64-72 bytes (DER) Ubiquitous in TLS; needs safe k (RFC 6979)
RSA-PSS RSA-2048+ 256+ bytes Modern RSA signatures; use over PKCS#1 v1.5
RSA PKCS#1 v1.5 RSA-2048+ 256+ bytes Legacy but everywhere (JWT RS256); acceptable
Ed448 Curve448 114 bytes Higher security margin, rarer
SLH-DSA / ML-DSA post-quantum 7 KB - 50 KB Part 6

What a signature does and does NOT prove

   PROVES:
     [x] the message wasn't altered after signing
     [x] whoever signed held the private key
     [x] (to a third party) that the key-holder signed -- non-repudiation

   DOES NOT PROVE:
     [ ] WHO the key-holder is         -> needs a certificate / web of trust
     [ ] WHEN it was signed            -> needs a trusted timestamp
     [ ] that the signer MEANT it / read it   ("what you see is what you sign")
     [ ] that the key isn't stolen or the signer isn't coerced
     [ ] freshness -- an old validly-signed message can be REPLAYED unless
         the signed content includes a nonce/timestamp

The gap between "valid signature" and "trustworthy message" is filled by PKI (Part 7) for identity, and by protocol design (nonces, timestamps, expiry) for freshness.

Where you'll use signatures

   TLS certificates        CA signs "this public key belongs to example.com"
   TLS handshake           server signs the ephemeral ECDHE params (authenticity)
   Code signing            Apple/Microsoft/distro signs binaries and packages
   Software updates        the update file is signed; the updater verifies
                             before installing (TUF, Sigstore)
   JWT (RS256/ES256/EdDSA) the token issuer signs the claims
   Git                     signed commits and tags (git commit -S)
   SSH                     server host key signs to prove identity; user key
                             signs to authenticate (Part 8)
   Blockchains             every transaction is a signature
   Secure boot             each stage verifies the next stage's signature

Worked example (run it)

cd ~/sec-lab/scratch

# --- Ed25519 end to end ---
openssl genpkey -algorithm ed25519 -out signer.pem
openssl pkey -in signer.pem -pubout -out signer.pub

cat > update.json <<'EOF'
{"version":"4.2.0","url":"https://cdn.example.com/app-4.2.0.tar.gz",
 "sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}
EOF

# sign
openssl pkeyutl -sign -inkey signer.pem -rawin -in update.json -out update.sig

# verify (this is what the updater does before trusting the manifest)
openssl pkeyutl -verify -pubin -inkey signer.pub -rawin -in update.json \
        -sigfile update.sig

# attacker swaps the download URL
sed -i.bak 's#cdn.example.com#evil.example#' update.json
openssl pkeyutl -verify -pubin -inkey signer.pub -rawin -in update.json \
        -sigfile update.sig            # FAILS -- the manifest is rejected
# --- MAC vs signature: the non-repudiation difference ---
.venv/bin/python - <<'PY'
import hmac, hashlib
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

msg = b"Alice agrees to pay Bob 500."

# HMAC: Bob ALSO has the key, so Bob could have made this himself.
k = b"shared-key"
tag = hmac.new(k, msg, hashlib.sha256).hexdigest()
print("HMAC tag:", tag[:32], "...")
print("  -> a judge can't tell if Alice or Bob produced it. No non-repudiation.")

# Signature: only Alice has the private key.
alice = Ed25519PrivateKey.generate()
sig = alice.sign(msg)
alice.public_key().verify(sig, msg)      # raises if invalid
print("\nEd25519 signature verified against Alice's PUBLIC key.")
print("  -> only Alice's private key could produce it. Non-repudiation.")
PY
# --- signed Git commits ---
# git config --global user.signingkey <key>
# git config --global commit.gpgsign true      # or gpg.format ssh + user.signingkey
git init -q demo && cd demo
git config gpg.format ssh
git config user.signingkey "$PWD/../signer.pub"
git config gpg.ssh.allowedSignersFile /dev/null
echo hi > f && git add f
git -c commit.gpgsign=true commit -q -S -m "signed commit" 2>&1 | head -2 || true
git log --show-signature -1 2>&1 | head -8
cd ..

Practice (35 min)

  1. Sign a release manifest with Ed25519; verify it; tamper with one byte of the manifest; confirm verification fails.
  2. Sign the same manifest with RSA-PSS and with ECDSA P-256. Compare signature sizes and openssl speed sign/verify rates across all three.
  3. Run the MAC-vs-signature script. Write, in notes.md, a two-sentence explanation of non-repudiation you could give a non-engineer.
  4. Set up SSH-based signed Git commits (gpg.format ssh). Make a signed commit. Break the signature by amending the message with --no-gpg-sign and see git log --show-signature flag it.
  5. Explain why "valid signature" does not mean "safe to run this binary". List three things still required.

Common confusions

  • "A signature encrypts the message." No. The message travels in the clear; the signature only proves integrity + origin. Combine with encryption if you need secrecy.
  • "Verifying a signature means I can trust the content." It means the content wasn't altered since signing by that key. Whether to trust that key is PKI's job.
  • "Sign then encrypt" vs "encrypt then sign". Both have pitfalls (identity stripping, surreptitious forwarding). Prefer a construction that binds identity into the signed/encrypted data, or use a vetted protocol.
  • "Old signatures stay valid forever, so replays are fine." A validly-signed message can be replayed. Put a timestamp/nonce/expiry inside the signed data.
  • "RSA signing = RSA decryption." Structurally similar, but different padding (PSS vs OAEP) and you must never share a key between the two roles.

Check yourself

  1. What does a signature give you that an HMAC does not?
  2. Why do you sign the hash of a message rather than the message itself?
  3. What does a broken hash function do to a signature scheme? Give the real example.
  4. List three things a valid signature does not prove.
  5. How do you stop a validly-signed message from being replayed?

Further reading

  • Book: Serious Cryptography (2nd ed.), Chapter 10 (Digital Signatures).
  • Project: Sigstore (sigstore.dev) — modern, keyless software signing with transparency logs. Read the "how it works" page.
  • Spec: The Update Framework (TUF), theupdateframework.io — how to design a software-update system that survives key compromise.
  • Article: "How (not) to sign a JSON object" (Latacora) — canonicalisation pitfalls in signing structured data.
  • Video: Computerphile, "Digital Signatures".

Generated from real-life-guide.md · 8 Oct 2026 16:36