Digital signatures and certificates
Why signing means hashing first, how RSA and ECDSA signatures differ, what a certificate chain proves, and how code signing and reproducible builds use hashes together.
You sign a digest, not a document
Public-key operations are slow and only work on small inputs, so a signature covers a hash of the message. That is why a broken hash breaks the signature even when the signature algorithm is sound.
sign: message -> hash -> sign(hash, private key) -> signature
verify: message -> hash -+
+-> verify(signature, public key) -> valid?
signature ---------+# generate a key pair and sign a digest
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out key.pem
openssl pkey -in key.pem -pubout -out pub.pem
openssl dgst -sha256 -sign key.pem -out file.sig file.bin
openssl dgst -sha256 -verify pub.pem -signature file.sig file.bin
# Verified OKRSA and ECDSA compared
| Aspect | RSA (PKCS#1 v1.5 or PSS) | ECDSA (P-256) | Ed25519 |
|---|---|---|---|
| Key size for 128-bit security | 3072 bit | 256 bit | 256 bit |
| Signature size | 384 bytes at 3072 | About 72 bytes (DER) | 64 bytes |
| Speed | Slow signing, fast verify | Fast both ways | Very fast both ways |
| Deterministic signature | Yes with PKCS#1 v1.5 | No, unless RFC 6979 is used | Yes by design |
| Nonce risk | None | A repeated nonce leaks the private key | None |
| Best for | Legacy interoperability | Existing web PKI | New protocols and tokens |
from cryptography.hazmat.primitives.asymmetric import ed25519
private = ed25519.Ed25519PrivateKey.generate()
public = private.public_key()
msg = b"release 1.2.3"
sig = private.sign(msg)
public.verify(sig, msg) # raises InvalidSignature if it does not match
# a signature over a digest is equally valid
import hashlib
digest = hashlib.sha256(msg).digest()
sig2 = private.sign(digest)
public.verify(sig2, digest)- Ed25519 is deterministic: no per-signature randomness, so no nonce-reuse catastrophe.
- ECDSA requires a unique nonce per signature; reuse has leaked private keys in real products.
- RSA-PSS is the modern RSA padding; the older PKCS#1 v1.5 remains in use for compatibility.
- Post-quantum signature schemes are being standardised now; plan migration for anything with a long trust lifetime.
Chains, code signing and reproducible builds
- A certificate binds a public key to a name, and is itself signed by a certificate authority whose key is in a trust store.
- Verification walks the chain to a trusted root, checks validity dates, and confirms the name matches.
- A signature over a software release ties the artifact to a publisher identity, not just to integrity.
- Reproducible builds let anyone recompute the artifact hash from source and compare it with the published one.
- Sign the hash of the release manifest, not only each file, so the set of artifacts is covered too.
# verify a release: signature, then digest, then the artifact
gpg --verify release.tar.gz.sig release.tar.gz
sha256sum -c SHA256SUMS
# inspect a certificate chain
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates⚠️
A signature is only as trustworthy as the key behind it. Publishing the public key next to the signature on the same server proves nothing about authorship — an attacker who can replace the file can replace both. Distribute keys through a separate channel.
FAQ
Why does my signature break when I re-encode the file?
Any change to the bytes changes the digest. Text editors, line-ending conversion or a re-zip step will invalidate it. Sign the exact artifact you distribute, and never edit it afterwards.
Is a hash alone enough to trust a download?
No. A hash detects accidental corruption, but an attacker who controls the download can also control the published hash. A signature from a key you obtained independently is the missing piece.
Related
HMAC and keyed hashing SHA-3, BLAKE2 and BLAKE3
Last refreshed 2026-09-18.