Chapter 22 — Hands-on: keys, signatures, and key exchange with OpenSSL
Why this chapter exists
Parts 7 and 8 (TLS, SSH) assume you can fluently generate keys, inspect them, sign, verify, and derive shared secrets. This is the drill. Do it end to end once and the rest of the guide gets easier.
The one setup step: get real OpenSSL
openssl version
# LibreSSL x.x.x -> you are on macOS's build; several commands below WILL FAIL
# OpenSSL 3.x.x -> you're good
# macOS fix:
brew install openssl@3
echo 'export PATH="'"$(brew --prefix openssl@3)"'/bin:$PATH"' >> ~/.zshrc
exec zsh
openssl version # should now say OpenSSL 3.x
Everything below is written for OpenSSL 3.x.
1. Generate every key type
mkdir -p ~/sec-lab/keys && cd ~/sec-lab/keys
# RSA 2048 and 4096
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa2048.pem
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out rsa4096.pem
# EC: NIST P-256 and P-384
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec256.pem
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out ec384.pem
# Modern: Ed25519 (signatures) and X25519 (key exchange)
openssl genpkey -algorithm ed25519 -out ed25519.pem
openssl genpkey -algorithm x25519 -out x25519.pem
# Derive every public key
for k in rsa2048 rsa4096 ec256 ec384 ed25519 x25519; do
openssl pkey -in $k.pem -pubout -out $k.pub
done
ls -l *.pem *.pub # note how much smaller the EC/25519 files are
2. Inspect keys
# human-readable dump
openssl pkey -in rsa2048.pem -text -noout | head -25
openssl pkey -in ec256.pem -text -noout
openssl pkey -in ed25519.pem -text -noout
# just the key type and size
openssl pkey -in rsa4096.pem -noout -text | grep -i 'private-key'
# public key only
openssl pkey -in ec256.pem -pubout -text -noout
# convert PEM <-> DER
openssl pkey -in ec256.pem -outform DER -out ec256.der
openssl pkey -in ec256.der -inform DER -text -noout | head -3
# encrypt a private key at rest (you'll be prompted for a passphrase)
openssl pkey -in rsa2048.pem -aes-256-cbc -out rsa2048.enc.pem
3. Sign and verify
cd ~/sec-lab/keys
echo "release 5.0.0 build a1b2c3" > artifact.txt
# RSA-PSS
openssl dgst -sha256 -sign rsa2048.pem -sigopt rsa_padding_mode:pss \
-out artifact.rsa.sig artifact.txt
openssl dgst -sha256 -verify rsa2048.pub -sigopt rsa_padding_mode:pss \
-signature artifact.rsa.sig artifact.txt
# ECDSA P-256
openssl dgst -sha256 -sign ec256.pem -out artifact.ec.sig artifact.txt
openssl dgst -sha256 -verify ec256.pub -signature artifact.ec.sig artifact.txt
# Ed25519 (no -dgst; sign the raw message)
openssl pkeyutl -sign -inkey ed25519.pem -rawin -in artifact.txt \
-out artifact.ed.sig
openssl pkeyutl -verify -pubin -inkey ed25519.pub -rawin -in artifact.txt \
-sigfile artifact.ed.sig
# prove tampering is caught
echo "release 5.0.0 build DEADBEEF" > artifact.txt
openssl pkeyutl -verify -pubin -inkey ed25519.pub -rawin -in artifact.txt \
-sigfile artifact.ed.sig # fails
4. Key exchange (ECDH / X25519)
cd ~/sec-lab/keys
# Two parties each generate an X25519 keypair
openssl genpkey -algorithm x25519 -out alice.pem
openssl genpkey -algorithm x25519 -out bob.pem
openssl pkey -in alice.pem -pubout -out alice.pub
openssl pkey -in bob.pem -pubout -out bob.pub
# Each derives the shared secret from their private key + the other's public key
openssl pkeyutl -derive -inkey alice.pem -peerkey bob.pub -out s_alice.bin
openssl pkeyutl -derive -inkey bob.pem -peerkey alice.pub -out s_bob.bin
xxd s_alice.bin
xxd s_bob.bin
cmp s_alice.bin s_bob.bin && echo "SHARED SECRETS MATCH"
# Turn the raw secret into an actual AES key with a KDF (don't use it directly)
openssl kdf -keylen 32 -kdfopt digest:SHA256 \
-kdfopt key:$(xxd -p -c256 s_alice.bin) \
-kdfopt info:$(echo -n 'secureshop session key' | xxd -p) HKDF
5. Benchmark everything
openssl speed -seconds 2 rsa2048 rsa4096 ecdsap256 ed25519 2>/dev/null | tail -12
openssl speed -seconds 2 ecdhx25519 ecdhp256 2>/dev/null | tail -6
Read the table. Internalise these rough facts:
* RSA signing is SLOW; RSA verifying is fast (asymmetric cost -- shapes
protocols: you verify certs often, CAs sign rarely).
* ECDSA / Ed25519 sign and verify are both fast and roughly balanced.
* Ed25519 is typically the fastest signer.
* X25519 key exchange is very cheap -- which is why every TLS 1.3
connection can afford a fresh one (forward secrecy is basically free).
Practice / mini-project (60 min) — "signed release" tool
Build a tiny end-to-end release-signing workflow. This is a real pattern you'll reimplement in production.
Requirements:
1. `keygen` -> produces an Ed25519 signing keypair; stores the private key
encrypted with a passphrase.
2. `sign <dir>` -> hashes every file in <dir> (SHA-256), writes a MANIFEST
file listing path + hash, then signs the MANIFEST.
3. `verify <dir>` -> checks the MANIFEST signature against the public key,
then re-hashes every file and confirms it matches.
Any mismatch, missing file, or extra file = FAIL.
Test it:
- Happy path verifies.
- Change one byte in one file -> verify fails, naming the file.
- Add a file not in the manifest -> verify fails.
- Corrupt the signature -> verify fails before hashing anything.
- Sign with key A, verify with key B -> fails.
You may write it in bash (openssl, sha256sum) or Python (cryptography).
Keep it under ~80 lines. Save it as ~/sec-lab/tools/release-sign. Part 12
extends this idea into SecureShop's deployment pipeline.
Common confusions
- "
openssl rsa/openssl ec/openssl dsa." These are the legacy per-algorithm commands. In OpenSSL 3.x,openssl genpkeyandopenssl pkeyhandle every type uniformly. Learn those. - "Ed25519 with
-sha256." Ed25519 hashes internally. Usepkeyutl -rawin, notdgst. - "The X25519 shared secret is my session key." Run it through HKDF first.
- "
openssl pkeyutl -deriveon macOS." LibreSSL lacks X25519. Get OpenSSL 3.x.
Check yourself
- Which single
opensslsubcommand generates an RSA, EC, or Ed25519 private key in 3.x? - Why does Ed25519 signing not use
openssl dgst? - After
openssl pkeyutl -derive, what must you do before using the output as a key? - From the
openssl speednumbers: is RSA signing or verifying the slow direction, and how does that shape TLS?
Further reading
- Book (free): OpenSSL Cookbook — Ivan Ristić. Chapters on key and CSR management are the reference for Parts 7–8.
- Cheat sheet: the "OpenSSL Essentials" post on the DigitalOcean community site — practical and correct.
- Man pages:
openssl-genpkey(1),openssl-pkey(1),openssl-pkeyutl(1),openssl-dgst(1),openssl-speed(1). - Appendix B of this guide — the OpenSSL cookbook tuned to everything here.
End of Part 5 — Milestone check
- I can explain the key-distribution problem and how public keys solve it
- I know which key encrypts-to-someone and which key signs
- I have done a toy Diffie-Hellman by hand and seen both sides agree
- I can explain forward secrecy and why TLS 1.3 mandates ECDHE
- I did toy RSA by hand: computed d, encrypted, decrypted, signed, verified
- I can state why textbook RSA is broken and what OAEP/PSS add
- I know why a 256-bit ECC key equals a 3072-bit RSA key
- I have seen a private key recovered from two ECDSA signatures with a reused k
- I can explain non-repudiation and when to use a signature over an HMAC
- I have generated every key type, signed, verified, and derived an ECDH shared secret with OpenSSL
- I built the signed-release tool