CyberNugget

L16 · Asymmetric Cryptography

Module 3: Cryptography Essentials · ~20 min · Lesson 16 of 71

🎯 What you'll learn

  • The two jobs of a key pair: encrypt to a public key, sign with a private key
  • Why asymmetric crypto is slow, and how hybrid encryption fixes that
  • RSA vs elliptic curves (P-256, Curve25519), and which key sizes give equal strength
  • How Diffie-Hellman lets two strangers agree on a secret in public (paint mixing, then the math)
  • What quantum computers change, and what ML-KEM in your TLS handshake is for

🔥 Why this matters

In March 2023, GitHub replaced its RSA SSH host key after the private half was briefly exposed in a public repository. Anyone holding it could have pretended to be github.com to your git push. Developers everywhere then saw REMOTE HOST IDENTIFICATION HAS CHANGED!. One leaked private key, and a trust relationship used by millions had to be rebuilt. Today is about what that key does, and why the other half could be printed on a billboard.

1. Two keys, two jobs

Symmetric crypto (L15) has one problem it can't solve alone: both sides need the same secret key first. How do you give a partner bank an AES key without someone copying it on the way?

Asymmetric cryptography (also called public-key cryptography) uses a key pair: two mathematically linked keys. The public key can be shared with anyone. The private key never leaves its owner. Knowing the public key doesn't let you work out the private one.

An analogy: you hand out open padlocks (public key) to anyone who asks, and you keep the only key that opens them (private key). Anyone can lock a box for you; only you can open it. Technically, the pair supports two different operations, and mixing them up is the most common beginner mistake:

 ENCRYPT TO someone            SIGN WITH your own key
 (confidentiality)             (integrity + authenticity)

 sender                        signer
   │ uses RECIPIENT'S            │ uses OWN private key
   │ public key                  ▼
   ▼                           message + signature
 ciphertext ──sent──▶            │ ──sent──▶ anyone
   │                             ▼
   ▼ recipient's               verifier uses SIGNER'S
   private key decrypts        public key: "valid" or not

 Only the recipient can read.  Anyone can check; only the
                               private-key holder could sign.

You met the right-hand side in L01 and L11: a CA signs a certificate, and your client verifies it with the CA's public key. Signing doesn't hide anything; it proves who produced the data and that it wasn't changed. That's integrity and authenticity, not confidentiality, the distinction L01 drilled. L17 covers signatures in depth.

2. Slow and small, so we go hybrid

Asymmetric math is expensive, and RSA can only encrypt a message shorter than its key. On this Mac (Apple M5 Pro, openssl speed), RSA-3072 managed about 1,100 private-key operations a second, while AES-256-GCM pushed about 9.5 GB a second. With RSA-3072 and OAEP-SHA-256 padding, the largest message you can encrypt is 318 bytes.

So real systems use hybrid encryption: asymmetric crypto moves a small random key, and symmetric crypto (an AEAD such as AES-GCM, L15) encrypts the actual data.

 sender                                         recipient
 1. random 256-bit AES key K
 2. AES-GCM(K, 50 MB file) ─── ciphertext ─────▶
 3. RSA-OAEP(recipient pub, K) ─ wrapped K ────▶ 4. private key
                                                    unwraps K
                                                 5. K decrypts
                                                    the file

TLS does the same thing with a key agreement instead of key wrapping (section 4). AWS KMS does a cousin of it called envelope encryption, which L19 covers.

3. RSA vs elliptic curves

RSA (1977) rests on one fact: multiplying two huge primes is easy, but factoring the product back into them is extremely hard. Your public key contains the product n; the private key is built from the primes. A toy version with tiny numbers:

 primes p=61, q=53       n = 3233 (public modulus)
 public exponent e = 17  private exponent d = 2753
 encrypt m=65:  65^17   mod 3233 = 2790
 decrypt:       2790^2753 mod 3233 = 65
 Attack: factor 3233 → 61 × 53, rebuild d. Trivial here;
 infeasible for a 3072-bit n with known classical methods.

Textbook RSA (that raw formula) is unsafe: the same message always gives the same ciphertext, and an attacker can multiply ciphertexts to make predictable changes. Real RSA adds randomized padding. For encryption use OAEP. The older PKCS#1 v1.5 encryption padding leaks through error messages (Bleichenbacher, 1998, and it was still found in big-vendor TLS stacks in 2017, the ROBOT attack). For signatures, PSS is preferred, though PKCS#1 v1.5 signatures are still widely used and not broken the same way.

Elliptic-curve cryptography (ECC) rests on a different hard problem (the elliptic-curve discrete logarithm) and needs far smaller keys for the same strength. The curves you'll meet are P-256 (a NIST curve, used by ECDSA and ES256) and Curve25519, used as X25519 for key agreement and Ed25519 for signatures. Equal strength, per NIST SP 800-57 Part 1 Rev. 5, Table 2:

Security strength Symmetric RSA key ECC key
112 bits (3DES, retired) 2048 224–255
128 bits AES-128 3072 256–383 (P-256, Curve25519)
192 bits AES-192 7680 384–511 (P-384)
256 bits AES-256 15360 512+ (P-521)

That's why an Ed25519 public key is 32 bytes while an RSA-3072 one is about 400. Pick Ed25519 or P-256 for new work unless something forces RSA; if it does, use 3072 bits or more.

4. Diffie-Hellman: a shared secret in public

Two people want the same secret, but every message between them is overheard. Diffie-Hellman (DH) solves it. The classic paint-mixing picture:

       Alice                public            Bob
  common: yellow  ◀──── agreed openly ────▶  common: yellow
  secret: red                               secret: blue
  mix → orange   ────── orange ──────▶
                 ◀────── green ───────      mix → green
  orange-from-Bob? no: green + red          orange + blue
         = brown                                = brown

 Eavesdropper has yellow, orange, green. Un-mixing paint to
 recover red or blue is the hard step, so no brown for them.

The precise version swaps paint for modular arithmetic. Public: a prime p and a base g. Alice picks secret a and sends A = g^a mod p; Bob picks b and sends B = g^b mod p. Each raises the other's value to their own secret: B^a = A^b = g^(ab) mod p. With p=23, g=5, a=6, b=15: Alice sends 8, Bob sends 19, and both compute 2. Recovering a from 8 is the discrete logarithm problem: easy at this size, infeasible at real sizes.

ECDH does the same on an elliptic curve. In L10's TLS 1.3 handshake, the key_share was an X25519 public value, and "ephemeral" (the E in ECDHE) meant fresh secrets for every connection, which is what gives forward secrecy. Plain DH doesn't prove who is on the other end, though; that's why the server also signs the handshake with its certificate key.

5. The quantum asterisk

Shor's algorithm (1994) would let a large enough quantum computer factor and solve discrete logs efficiently, breaking RSA, ECC and DH alike. No such machine exists yet, but harvest now, decrypt later is a threat today: record encrypted traffic now, decrypt it once the machine arrives. AES and SHA-2 are much less affected.

NIST published replacements in August 2024: FIPS 203 (ML-KEM) for key establishment, FIPS 204 (ML-DSA) and FIPS 205 (SLH-DSA) for signatures. The X25519MLKEM768 group you saw offered in L10 is a hybrid: it runs X25519 and ML-KEM-768 and combines both secrets, so an attacker must break both. The first deep dive covers what to do about it.

🧪 Worked example: who holds which key in a typical stack

Where Key pair Private key lives Public key goes to
git push over SSH Your Ed25519 key Your laptop (+ agent) GitHub, Settings → SSH keys
GitHub's identity GitHub's host key GitHub's servers Your ~/.ssh/known_hosts
Signed commits Ed25519 signing key Your laptop GitHub, as a signing key
Kong JWT check IdP's RS256/ES256 key The identity provider Kong, via a JWKS URL
KMS signing ECC_NIST_P256 KMS key Inside KMS (never exported) Anyone verifying

The JWKS (JSON Web Key Set) that a gateway such as Kong, or a service, fetches shows the size difference directly (values shortened, IDs made up):

{"keys": [
  {"kty": "RSA", "kid": "rsa-2026-01", "alg": "RS256",
   "e": "AQAB", "n": "sXch...Qw (342 chars for RSA-2048)"},
  {"kty": "EC", "kid": "ec-2026-01", "alg": "ES256",
   "crv": "P-256", "x": "f83O...J0", "y": "x_FE...4E"}
]}

RS256 is an RSA (PKCS#1 v1.5) signature with SHA-256; ES256 is ECDSA on P-256. Both are signatures: the token is readable by anyone, and the key pair only proves the IdP issued it. (L25 covers JWT pitfalls.) The same rule holds for a KMS key such as aws kms create-key --key-spec ECC_NIST_P256 --key-usage SIGN_VERIFY: callers with kms:Sign can produce signatures, but nobody can download the private key.

🛠 Try it (optional, ~5 min)

Make a throwaway key pair in a temp directory. This never touches ~/.ssh. -N '' means "no passphrase", fine for a demo key you'll delete, never for a real one. ssh-keygen ships with macOS; the output below is from this Mac (OpenSSH 10.5p1).

$ cd "$(mktemp -d)"
$ ssh-keygen -t ed25519 -C "you@example.com" -f ./demo -N ''
Generating public/private ed25519 key pair.
...
The key fingerprint is:
SHA256:upmzuUPwPPgVfkIZ8nFDVlpQLbJwILGn8oy6povBzaI you@example.com

$ cat demo.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICxnfJPPSR7e0kTDDqBLiCL7gr4
aTEKNwAjNa6PkltYF you@example.com          (one line, wrapped)
$ ls -l demo demo.pub
-rw-------  1 you  wheel  411 Nov  6 18:44 demo
-rw-r--r--  1 you  wheel   97 Nov  6 18:44 demo.pub
$ ssh-keygen -y -f demo      # rebuild public from private
$ ssh-keygen -lf demo.pub    # fingerprint again
256 SHA256:upmz...BzaI you@example.com (ED25519)

What to look for:

  • The private file is -rw------- (owner only); the public one is world-readable, on purpose.
  • ssh-keygen -y recreates the public key from the private key. The reverse is impossible.
  • The fingerprint is a SHA-256 hash (L14) of the public key: a short ID to compare by eye.
  • Run it again with -t rsa -b 3072 and compare: the RSA .pub file was 569 bytes here, versus 97.
  • When you're done, delete the temp directory (cd .. && rm -rf the path mktemp printed).

🧠 Key takeaways

  • Encrypt to someone's public key; sign with your own private key. Different jobs, different keys.
  • Asymmetric crypto is slow and size-limited, so it only moves keys; AES-GCM moves the data.
  • RSA needs 3072 bits for 128-bit strength; Ed25519 and P-256 get there with 256-bit keys.
  • Diffie-Hellman agrees on a secret in public; ECDHE in TLS adds forward secrecy.
  • Quantum computers would break RSA, ECC and DH alike; hybrid ML-KEM is already in your handshakes.

📖 Words to know

Term Meaning
Key pair A linked public key (share it) and private key (guard it)
Hybrid encryption Asymmetric crypto protects a random symmetric key; that key encrypts the data
OAEP Randomized padding that makes RSA encryption safe; use it instead of PKCS#1 v1.5
ECC Elliptic-curve cryptography: same strength as RSA with much smaller keys
Ed25519 / X25519 Curve25519 used for signatures / for key agreement
Diffie-Hellman (DH, ECDH) Two parties derive a shared secret from public values plus their own secrets
Fingerprint A short hash of a public key, used to recognize it
Harvest now, decrypt later Recording encrypted traffic today to break it with a future quantum computer
ML-KEM NIST's post-quantum key-establishment standard (FIPS 203)

⏭ Next time

L17 · Digital signatures and MACs: what a signature actually proves, how HMAC does a similar job with a shared key, and how to verify a webhook so nobody can fake one.

Deep dive 1

Post-quantum crypto: what changes and what you should do now

What a quantum computer would actually break

Two quantum algorithms matter, and they hit very different targets:

 Shor (1994)  factoring + discrete logs ──▶ BROKEN
              RSA, DH, ECDH, ECDSA, Ed25519, X25519

 Grover (1996) brute-force search speed-up ──▶ WEAKENED
              AES-256 behaves like ~128-bit: still fine
              SHA-256, HMAC-SHA-256: still fine

So the problem is almost entirely public-key crypto: key exchange and signatures. Your AES-256-GCM data at rest (and KMS's SYMMETRIC_DEFAULT keys, which are AES-256-GCM) aren't the worry.

The two halves have different deadlines:

  • Key exchange is urgent. A recording of today's TLS session can be decrypted whenever a quantum computer appears. That's harvest now, decrypt later.
  • Signatures are less urgent. A forged signature only helps an attacker after the machine exists, and you can switch signature algorithms before then. Long-lived trust anchors (root CAs, firmware signing keys) are the exception, because they take years to replace.

Is your data's shelf life longer than the clock?

Michele Mosca's rule of thumb puts it as three numbers:

 X = years the data must stay secret
 Y = years it takes you to migrate
 Z = years until a quantum computer can break it

 if X + Y > Z  ──▶ you are already late

 now ──────────[ Y: migrate ]───────▶
 now ─────────────────[ X: data must stay secret ]──────▶
 now ─────────────────────────[ Z: ? ]──▶  overlap = loss

Account numbers, policy records and tax IDs have an X of decades, and Z is honestly unknown. That's why financial firms treat PQ key exchange as a now problem.

The new standards (August 2024)

Standard Name Job Replaces Size (this Mac, OpenSSL 4.1)
FIPS 203 ML-KEM (from Kyber) Key establishment ECDH, RSA key transport ML-KEM-768 public key 1,184 B vs X25519 32 B
FIPS 204 ML-DSA (from Dilithium) Signatures ECDSA, Ed25519, RSA ML-DSA-65 signature 3,309 B vs Ed25519 64 B
FIPS 205 SLH-DSA (from SPHINCS+) Signatures, hash-based backup same Bigger and slower still; a conservative fallback

A KEM (key encapsulation mechanism) is a slightly different shape from DH: the client sends a public key, the server "encapsulates" a fresh secret to it and returns a ciphertext, and both end up with the same secret. Size is the main cost you'll notice: kilobytes where you had bytes.

Hybrid: belt and braces

ML-KEM is new, and new schemes sometimes fall to classical attacks. So TLS combines it with the old one:

 X25519MLKEM768 key_share (client → server, ~1,216 bytes)
   ├── ML-KEM-768 public key  (1,184 B) ──▶ secret S1
   └── X25519 public key      (   32 B) ──▶ secret S2
 session keys derived from S1 ‖ S2
 attacker must break BOTH ML-KEM and X25519

Where you'll see it today:

  • Browsers. Chrome has sent X25519MLKEM768 by default since version 131 (November 2024). Cloudflare has reported that a majority of human web traffic to it now uses PQ key exchange.
  • OpenSSL 3.5+ offers it by default. Check with openssl list -kem-algorithms (Homebrew OpenSSL; macOS's /usr/bin/openssl is LibreSSL and doesn't have the list command).
  • OpenSSH 10.0+ uses mlkem768x25519-sha256 by default; 10.1+ warns when a connection falls back to a non-PQ exchange. Run ssh -Q kex | grep mlkem.
  • AWS. Since November 2025, ALB and NLB offer opt-in PQ TLS policies, such as ELBSecurityPolicy-TLS13-1-2-Res-PQ-2025-09. KMS now has ML_DSA_* and ML_KEM_* key specs.

The official clock

NIST IR 8547, finalized in October 2026, sets the schedule: RSA, ECDSA, EdDSA, DH and ECDH are deprecated after 2030 (still allowed, but you must justify the risk) and disallowed after 2035, at every key size. US federal high-value systems face earlier deadlines (PQ key establishment by the end of 2030). Even if your organization isn't federal, regulators and auditors tend to borrow these dates.

🧪 Worked example: a PQ to-do list for a typical API stack

  1. Inventory. List where public-key crypto runs: ALB/NLB listeners, the gateway's TLS and mTLS (Kong in the examples, L11), service-to-database TLS, SSH to bastions and GitHub, JWT signing keys, KMS asymmetric keys, partner SFTP/PGP feeds. You can't migrate what you can't find.
  2. Key exchange first, on long-lived data. Move internet-facing listeners carrying sensitive customer data, such as financial records, to a PQ policy, and check your clients (JDK, Python ssl, Go) can negotiate it.
  3. Crypto agility. Make algorithms configuration, not code. A JWT verifier that reads alg and the key from a JWKS (with an allow-list) can switch later; one with RS256 hard-coded can't.
  4. Signatures later, but plan. Track your IdP's, CA's and code-signing vendors' ML-DSA roadmaps. Certificates will grow, so watch header and handshake size limits.
  5. Don't touch AES-256 just for quantum. It's not the weak link.

🛠 Try it (~3 min)

$ openssl version
OpenSSL 4.1.0 23 Oct 2026 (Library: OpenSSL 4.1.0 23 Oct 2026)
$ openssl list -kem-algorithms | grep -i mlkem
  X25519MLKEM768 @ default
  X448MLKEM1024 @ default
  SecP256r1MLKEM768 @ default
$ ssh -Q kex | grep mlkem
mlkem768nistp256-sha256
mlkem768x25519-sha256

If your OpenSSL is older than 3.5, the first list will have no ML-KEM entries; that's the inventory problem in miniature.

🧠 Extra takeaways

  • Quantum breaks public-key crypto (Shor), not AES-256 or SHA-256 (Grover only dents them).
  • Key exchange is urgent because of harvest now, decrypt later; signatures can follow.
  • Hybrids like X25519MLKEM768 keep you safe if either half holds.
  • NIST IR 8547: classical public-key crypto deprecated after 2030, disallowed after 2035.
Deep dive 2

SSH keys done right: ed25519, agents, and signing Git commits

Pick the right key type

 ed25519        ✔ default choice: small, fast, no tricky params
 ed25519-sk     ✔✔ private key lives on a FIDO2 security key;
                   a laptop compromise can't copy it
 ecdsa (P-256)  ✔ fine; use if policy requires NIST curves
 rsa ≥ 3072     ~ only for old servers that lack ed25519
 rsa 1024/dsa   ✘ weak; DSA was removed from OpenSSH 10.0

ssh-keygen -t ed25519-sk needs a hardware key that supports FIDO2. You'll touch it for each use, which also defeats malware quietly using your key. This Mac's OpenSSH 10.5 even lists an experimental hybrid ssh-mldsa44-ed25519@openssh.com key type, a sign of where SSH is heading.

Passphrase + agent: protected and convenient

A private key file with no passphrase is a password written on a sticky note: anyone who copies ~/.ssh/id_ed25519 from a backup or a stolen laptop is you on GitHub. Give real keys a passphrase, and let an SSH agent hold the unlocked key in memory so you type it once:

 ┌──────── your laptop ────────┐
 │ ~/.ssh/id_ed25519            │       ┌───────────┐
 │  (encrypted with passphrase) │       │  GitHub   │
 │        │ unlock once         │       │           │
 │        ▼                     │ 1 challenge       │
 │ ssh-agent (key in memory) ◀──┼───────┤           │
 │        │ 2 signs challenge   │       │           │
 │        └─────────────────────┼──────▶│ 3 verifies│
 └──────────────────────────────┘ sig   │ with your │
   private key never leaves             │ public key│
                                        └───────────┘

On macOS, ssh-add --apple-use-keychain ~/.ssh/id_ed25519 stores the passphrase in Keychain, and UseKeychain yes in ~/.ssh/config reuses it. The server sends a challenge, and your agent signs it: SSH login is a signature, not encryption to a public key.

Agent forwarding (ssh -A) is the trap. It lets the remote host ask your agent to sign things. Anyone with root on that bastion can use your key to reach GitHub as you while you're connected. To hop through a bastion, use ssh -J bastion target (ProxyJump) instead: the connection is tunnelled, and your agent stays on your laptop.

Check the other side's key too

The first time you connect, SSH shows the server's host-key fingerprint and asks if you trust it (TOFU, trust on first use). Compare it with the fingerprints GitHub publishes in its docs before typing yes. After GitHub's 2023 RSA host key rotation, the right response to REMOTE HOST IDENTIFICATION HAS CHANGED! was to check GitHub's announcement and remove the old line with ssh-keygen -R github.com, not to blindly delete known_hosts.

Signing Git commits with the same tools

Git's user.email is just a string. Anyone can make a commit that claims to be you. Since Git 2.34 you can sign commits with an SSH key, and GitHub shows Verified when the signature matches a signing key on your account:

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global gpg.ssh.allowedSignersFile \
  ~/.config/git/allowed_signers

Then add the same public key in GitHub under Settings → SSH and GPG keys as a Signing Key (authentication and signing are separate entries). Branch protection rules can then require signed commits on main, and GitHub's vigilant mode flags unsigned commits that claim to be yours.

🧪 Worked example: sign, verify, tamper (run on this Mac)

ssh-keygen -Y is the signing engine Git uses. With the demo key from today's Try it:

$ echo "deploy main @ 3f2a9c1" > msg.txt
$ ssh-keygen -Y sign -f demo -n file msg.txt
Signing file msg.txt
Write signature to msg.txt.sig

$ echo "you@example.com $(cat demo.pub)" > allowed_signers
$ ssh-keygen -Y verify -f allowed_signers \
    -I you@example.com -n file -s msg.txt.sig < msg.txt
Good "file" signature for you@example.com with ED25519
key SHA256:upmzuUPwPPgVfkIZ8nFDVlpQLbJwILGn8oy6povBzaI

$ echo "deploy main @ 3f2a9c2" | ssh-keygen -Y verify \
    -f allowed_signers -I you@example.com -n file \
    -s msg.txt.sig
Signature verification failed: incorrect signature
Could not verify signature.

One changed character, and verification fails (exit code 255). Notice what didn't happen: the message was never hidden. Anyone can read msg.txt. The signature protects integrity (was it changed?) and authenticity (who wrote it?), not confidentiality. Git commit signing works the same way, with -n git as the namespace so a file signature can't be replayed as a commit signature.

🧠 Extra takeaways

  • Use ed25519 (or ed25519-sk on a hardware key) with a passphrase, unlocked through an agent.
  • Prefer ssh -J to ssh -A; forwarding lends your key to whoever controls the remote host.
  • Verify host-key fingerprints against the provider's published list; don't just type yes.
  • SSH commit signing proves who made a commit and that it wasn't altered, not that it's secret.

The quiz for this lesson and the Tests section need JavaScript.