What A Signature Proves
A valid digital signature on a message proves three things: the signer had access to the private key (authenticity), the message was not modified after signing (integrity), and the signer cannot later deny having signed (non-repudiation).
One thing it does not prove: confidentiality. A signed message is not encrypted. Anyone can read it. Signing and encrypting are independent operations; you can do either, both, or neither, depending on what you need.
| Property | What it means |
|---|---|
| Authenticity | The signature could only have been produced by someone with the private key. |
| Integrity | Any change to the message, even a single bit, invalidates the signature. |
| Non-repudiation | The signer cannot credibly claim they did not sign, because verification is publicly checkable. |
Sign With Private, Verify With Public
The directional rule is the mirror of encryption:
- To encrypt a message to someone, you use their public key. Only they can decrypt.
- To sign a message as yourself, you use your own private key. Anyone with your public key can verify.
This is the source of the most common student confusion in asymmetric crypto: "wait, you encrypt with public but sign with private?" Yes. Same key pair, opposite directions, opposite security properties. The mnemonic from the Foundations page bears repeating: encrypt to a public key, sign with a private key.
Hash First, Then Sign
No real signing algorithm signs the raw message. They all hash the message first, then sign the hash. Three reasons:
- Size. RSA can only sign a value smaller than the modulus. A 2048-bit RSA key cannot sign a multi-megabyte file directly. SHA-256 of the file fits in 256 bits, which fits in any RSA modulus.
- Speed. Hash functions process gigabytes per second. The expensive asymmetric operation runs on a small fixed-size input.
- Security. Some signature algorithms have algebraic structure that an attacker can exploit by choosing messages with specific mathematical properties. Hashing first destroys that structure.
The pattern is universal:
signature = sign(private_key, SHA-256(message))
valid = verify(public_key, SHA-256(message), signature)
The hash function is part of the algorithm name: SHA256withRSA, ECDSA-P256-SHA256, Ed25519 (which has its own internal hash). When you see those strings in a certificate or JWT header, that is the hash-then-sign pattern in action.
Sign, Tamper, Verify
The interactive below uses a toy signing function. The math is not real ECDSA, but the protocol behavior is the same: any modification to the message invalidates the signature, and a signature from the wrong key fails verification.
Sign a message, then watch verification fail when the message is tampered
Type a message, click Sign to produce a signature using Alice's private key. Then edit the message (even one character) and click Verify. The check fails because the signed message no longer matches what is being verified.
RSA Signatures vs ECDSA vs Ed25519
Three signature families dominate the modern landscape. Each has different tradeoffs.
| Family | Signature size | Speed | Caveats |
|---|---|---|---|
| RSA-PSS (2048-bit) | 256 bytes | Slow signing, fast verifying | Padding scheme matters. Old PKCS#1 v1.5 has known attacks. |
| ECDSA-P256 | 64-72 bytes (DER-encoded) | Fast both ways | Requires a fresh random k per signature. Reusing k leaks the private key. |
| Ed25519 | 64 bytes | Very fast both ways | Deterministic by design. No k-reuse risk. Recommended default. |
Difference From Encryption
One sentence to internalize: encryption hides content; signatures prove origin and integrity. They are orthogonal. You can have any combination:
- Unsigned, unencrypted: a public blog post. Anyone can read, anyone could claim to be the author.
- Signed, unencrypted: a Linux kernel release. Public download, but you can verify it came from kernel.org.
- Unsigned, encrypted: a TLS-encrypted form submission to a server. The server reads it, but anyone could have submitted it.
- Signed and encrypted: a PGP-protected email with a verified sender. Encrypted to the recipient, signed by the sender.
Where Signatures Live
- TLS certificates: Each X.509 certificate carries a signature from a Certificate Authority. Browsers verify the chain on every connection. (See the PKI page.)
- Code signing: macOS, Windows, iOS, and Android require apps to be signed before they will run. The signature is checked against a developer certificate.
- Git commits and tags: Signed with GPG or SSH keys to prove the commit author. GitHub displays a "Verified" badge.
- JWT (JSON Web Tokens): The token payload is signed. Servers verify the signature instead of re-checking the user's password on every API call.
- DKIM email: Outbound mail servers sign messages with a domain key. Receiving servers verify by looking up the public key in DNS.
- Cryptocurrency transactions: Every Bitcoin or Ethereum transaction is an ECDSA signature authorizing the spend from a specific address.
Common Pitfalls
Sony signed PS3 firmware with ECDSA-P224. Their implementation reused the same random value k across multiple signatures. With two signatures sharing the same k, an attacker can solve a pair of linear equations and recover the private signing key. The fail0verflow team did exactly that and presented it at 27C3. After that talk, anyone could sign code that would run on a stock PS3. The lesson: ECDSA without per-signature random k is fatal. Ed25519 fixed this by making the equivalent value deterministic from the message and private key, so there is no random source to mishandle.
Many JWT libraries had a bug where a token claiming "alg": "none" would be accepted with no signature check. Attackers could forge any token. The library was technically doing what the token asked, which is not the same as doing what the developer intended. Always pin the expected algorithm on the verifier side.