Offered by Dead Brands
A digital signature proves three things at once: the data is unchanged (integrity), it came from the holder of a specific private key (authenticity), and the signer cannot credibly deny it later (non-
Use it over MCP: connect to https://mcp.deadbrands.co/hub/mcp and call skills_get with slug digital-signatures-and-verification.
# Digital Signatures and Verification
Credit: SteTy — published free in the Skills Hub
A digital signature proves three things at once: the data is unchanged (integrity), it came from the holder of a specific private key (authenticity), and the signer cannot credibly deny it later (non-repudiation) — the property a MAC can never give, since every MAC verifier also holds the power to forge. This skill covers the three signature families bots meet daily, the sign/verify workflows around them, the canonicalization traps that make valid data fail verification, and what a complete verification checklist looks like.
## Overview
Every modern scheme follows the same shape: hash the message with a strong hash, then apply the signature algorithm to the digest with the private key; the verifier recomputes the digest and checks it against the signature using the public key. The differences that matter are in the padding/randomness design, the sizes, and the failure modes — and the failure modes are legendary: ECDSA's per-signature random nonce has destroyed real keys when generated badly, while Ed25519 was designed specifically so that entire class of bug cannot happen. In practice you will sign and verify through containers — CMS/PKCS#7, OpenPGP, JWS/JWT, SSH signatures, code-signing envelopes — but the checklist discipline is identical everywhere: know exactly which bytes were signed, which key should have signed them, and whether that key was valid *at signing time*.
## Core Principles
- **You verify bytes, not meanings.** A signature covers one exact byte string. Any re-serialization — reordered JSON keys, changed line endings, re-encoded numbers — produces different bytes and a failed verification, even though "the data" looks identical.
- **Key identity precedes signature validity.** A mathematically valid signature from the *wrong* key is worthless. Always bind verification to an expected key or a validated certificate chain, never to "any key that verifies."
- **Validity is evaluated at signing time.** Keys expire and get revoked. A signature made while the key was valid stays valid — which is why trusted timestamps (RFC 3161) exist: they prove *when* the signature was made.
- **Signatures are not encryption.** Signing does not hide content, and "decrypting with the public key" is a textbook shorthand that is literally true of nothing in modern schemes (and never of EdDSA/ECDSA).
- **Deterministic beats random.** Where a scheme offers deterministic nonce generation (Ed25519 always; ECDSA via RFC 6979), prefer it — it removes the RNG from the trusted computing base at signing time.
## Techniques
**The three families.**
- **Ed25519** (EdDSA, RFC 8032): the default for new systems. 32-byte seed/private key, 32-byte public key, fixed 64-byte signature, ~128-bit security. Deterministic by construction — the nonce is derived by hashing the private key with the message, so no RNG is consulted and repeat-signing is byte-identical. Fast sign and verify, constant-time implementations are the norm. Sibling: Ed448 (57-byte keys, 114-byte signatures, ~224-bit security).
- **ECDSA** (P-256 / P-384): the incumbent — TLS certificates, JWT `ES256`, most blockchain systems. Signature is a pair (r, s), ~70–72 bytes DER-encoded for P-256. Its hazard is structural: each signature needs a fresh secret nonce *k*; reuse or bias in *k* leaks the private key algebraically. Demand RFC 6979 deterministic ECDSA or a library with hedged signing. Also note **malleability**: (r, n−s) is a second valid signature for the same message — protocols that need uniqueness enforce low-s.
- **RSA-PSS** (RFC 8017): the modern RSA signature padding — probabilistic (salted), the only RSA padding to use for new signatures (`RS256`/`PS256` in JWT). RSA-2048 gives a 256-byte signature and only ~112-bit security; RSA-3072 ≈ 128-bit. **PKCS#1 v1.5 signatures** are legacy: deterministic and historically forgeable when verifiers parse padding laxly (Bleichenbacher '06 class) — verify-only, with a strict modern library. RSA verifies fast, signs slow, and produces big artifacts; choose it for compatibility (certificates, TPMs, FIPS estates), not for new designs.
**Canonicalization — where verification goes to die.** The signer signed *some* bytes; the verifier must reproduce *those exact* bytes.
- **JSON:** key order, whitespace, and number formatting (`1e2` vs `100` vs `100.0`) are all serializer-dependent. Sign canonical JSON — JCS, RFC 8785 (lexicographic key sort, ECMAScript number rules) — or sign the raw payload detached (the JWS pattern: signature over the exact transmitted bytes, never over a re-serialization). Parsing into native types (dates, big integers) before re-serializing silently changes bytes; canonicalize pure JSON values only.
- **XML:** use Exclusive XML Canonicalization (C14N) as XML-DSig prescribes; whitespace and namespace declarations are the usual killers.
- **Text:** normalize line endings (CRLF vs LF) and trailing newlines *before* signing, and document the choice; hash what you sign, sign what you hash.
- **Structured data generally:** length-prefix or unambiguously delimit fields before hashing — naive concatenation makes `("ab","c")` and `("a","bc")` identical inputs.
**Attached vs detached.**
- **Attached / enveloped:** signature travels with the data — CMS SignedData, a `.sig` block appended to firmware, signed JWTs (data in the payload). Convenient; verify by opening the envelope.
- **Detached:** a separate signature file over the artifact's bytes (`.sig`, `.asc` next to a release tarball). The artifact stays byte-identical to the unsigned original — required when the signed thing is itself distributed through channels that must not modify it. Your workflow must pair them rigorously: signature of file A presented with file B is the oldest substitution trick there is.
- **Clear-signed** (PGP-style): human-readable text with an armor signature block; canonicalization includes text-mode line-ending rules — a frequent source of "valid signature, broken by my editor" bugs.
**Timestamping (RFC 3161).** Send the *hash* of the signed data to a Time Stamp Authority; it returns a signed token binding that hash to a UTC time from its audited clock, under a stated policy, with a unique serial. Store the token with the signature. Effect: after the signer's certificate expires or is later revoked, the timestamp still proves the signature existed while the key was valid. Note its limits: a token proves the hash existed at that time — not who held it, and not that the content is true. Long-term validation formats (PAdES-LT/LTV, CAdES) embed timestamps plus revocation evidence so verification works fully offline years later.
**The verification checklist** — run all of it, fail closed on any step:
1. Identify the exact signed bytes; recompute the digest with the stated hash (SHA-256+).
2. Obtain the public key from a trusted source; confirm its fingerprint/identity out-of-band or via a validated chain — not from the same untrusted message that carried the signature.
3. Check the key's validity *at signing time* (certificate dates, revocation status, or the RFC 3161 token's time).
4. Confirm the algorithm is on your allow-list (Ed25519, ECDSA P-256/P-384 with RFC 6979, RSA-PSS ≥ 2048) — reject downgrade surprises like an unexpected `none` or HS256-with-public-key confusion in JWTs.
5. Verify the signature cryptographically with a maintained library.
6. Then, and only then, interpret the content — and log key ID, algorithm, digest, and timestamp outcome for audit.
## Worked Examples
**The nonce failure that defines ECDSA risk.** In 2010 the PlayStation 3's ECDSA implementation used a constant *k*. Two signatures with the same *k* give two equations in two unknowns (k and the private key d) — high-school algebra recovers d, and the platform's root signing key fell. Lesson encoded in modern practice: deterministic RFC 6979 ECDSA or Ed25519, no exceptions for hand-rolled signers.
**Release signing with detached signatures + timestamp.** Hash the release archive (SHA-256), sign the digest with the release Ed25519 key → `release.tar.gz.sig`; request an RFC 3161 token over the signature; publish archive + signature + token + the public key fingerprint through a separate channel. Verifiers run the six-step checklist; the token keeps the release verifiable after the annual key rotation.
**A canonicalization bug, diagnosed.** Two services sign and verify the same JSON order. Signer emits keys sorted; verifier's framework re-serializes with insertion order and formats `250.0` as `250`. Digests differ, verification fails, "the signature is bad" is reported — but the signature is fine; the bytes moved. Fix: both sides adopt JCS (RFC 8785) canonical bytes before hashing, pinned by shared test vectors including `1e2` vs `100` cases.
## Common Pitfalls
- Verifying with whatever key arrived alongside the data (self-asserted trust).
- Algorithm confusion: accepting HMAC verification keyed with an RSA/ECDSA public key (the JWT `alg` swap), or accepting `alg: none`.
- Re-serializing parsed data before verifying a detached signature over the original bytes.
- Checking signature validity but not key validity — expired or revoked keys accepted silently.
- Assuming a local "signing time" field is evidence; only an RFC 3161 token (or transparency-log entry) anchors time independently.
- Rolling your own padding/parsing for RSA v1.5, or accepting non-canonical DER/low-s variants where uniqueness matters.
- Signing user-controlled bytes without domain separation — prefix signed payloads with a context string (`"app-name v1:"`) so signatures cannot be replayed across protocols.
## Practice Drills
1. Sign the same message twice with Ed25519 and twice with classic random ECDSA; compare outputs and explain, in terms of nonce generation, why one pair is identical.
2. Take a small JSON document, produce three byte-different serializations, hash each, and then canonicalize all three with JCS to a single digest.
3. Write the verification runbook for a software update: enumerate the six checklist steps with the exact failure action for each.
4. Given a "signature invalid" bug report, list the five most likely causes (bytes changed, wrong key, wrong algorithm parameters, expired-at-signing-time unproven, encoding of the signature itself) and one discriminating test per cause.
## When to Use and Limits
- **Use it when:** a task calls for digital signatures and verification, as laid out in the Techniques and Worked Examples above.
- **Watch first for:** (see Common Pitfalls).
- **Limits:** this is a general reference drawn from public sources; check exact values against the current standard or vendor docs before relying on them, and get expert review for production security decisions.
## Quick Reference
| Scheme | Key sizes | Signature | Nonce design | Use for |
|---|---|---|---|---|
| Ed25519 | 32 B / 32 B | 64 B | Deterministic (built-in) | New systems, SSH, releases |
| ECDSA P-256 | 32 B / 64 B | ~71 B DER | Random or RFC 6979 | TLS, JWT ES256, chains |
| RSA-PSS 3072 | 384 B modulus | 384 B | Salted, probabilistic | PKI compatibility, FIPS |
| RSA PKCS#1 v1.5 | — | = key size | Deterministic | Verify legacy only |
Standards: RFC 8032 (EdDSA), RFC 6979 (deterministic ECDSA), RFC 8017 (RSA-PSS), RFC 8785 (JCS canonicalization), RFC 3161 (timestamping). Rule that survives every incident review: pin the key, pin the bytes, pin the time.
---
*Trademarks and copyrights mentioned belong to their respective owners. Educational reference only; not affiliated with or endorsed by them.*