<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>words.filippo.io</title>
  <subtitle>Browse aymeric&#39;s, an independent, searchable archive of readable snapshots from followed feeds, preserved in Git with aggr.</subtitle>
  <id>https://aggr.aymericbeaumet.com/sources/words.filippo.io/</id>
  <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/sources/words.filippo.io/"/>
  <link rel="self" type="application/atom+xml" href="https://aggr.aymericbeaumet.com/sources/words.filippo.io/atom.xml"/>
  <updated>2026-10-16T20:18:36+00:00</updated>
  <author>
    <name>aymeric&#39;s</name>
  </author>
  <generator version="2.0.0" uri="https://github.com/aymericbeaumet/aggr">aggr</generator>
  <entry>
    <id>urn:aggr:item:7bd7f68f4f2a2ff1f6d654fa288f8a341a93ceb6</id>
    <title type="text">The NIST Elliptic Curves Seeds Bounty Has Been Claimed</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/lobste-rs/2026-10-16-the-nist-elliptic-curves-seeds-bounty-has-been-claimed/"/>
    <link rel="via" type="text/html" href="https://words.filippo.io/seeds-solved/"/>
    <published>2026-10-16T20:18:36+00:00</published>
    <updated>2026-10-16T20:18:36+00:00</updated>
    <author>
      <name>words.filippo.io by FiloSottile</name>
    </author>
    <category term="news"/>
    <category term="cryptography"/>
    <source>
      <title>Lobsters: Top Stories of the Past Week</title>
    </source>
    <summary type="text">Comments</summary>
    <content type="html">&lt;p&gt;We now know how the seeds of the NIST elliptic curves were generated: they’re not the hash of some saucy sentence, but the output of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://man.archlinux.org/man/drand48.3.en&quot;&gt;drand48&lt;/a&gt; seeded with the time, which was indeed in 1997–98.&lt;/p&gt;
&lt;p&gt;John Wood, Henry de Valence, Ava Howell, Zack Voase, Lucy Sweet, Amir Omidi, Paul Crowley, Iain McGinniss, Nicholas Carlini, and Steve Weis from Anthropic &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.anthropic.com/research/ecdsa-curve-origins&quot;&gt;solved the riddle&lt;/a&gt; and on October 15th, 2026, at 01:07:51 UTC, claimed &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/seeds-bounty/&quot;&gt;the full bounty I announced three years ago&lt;/a&gt;. They chose to donate the prize to &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://isrg.org/&quot;&gt;ISRG&lt;/a&gt;, the non-profit that operates Let’s Encrypt, tripling its value to $36,864 (36 Ki$). Thank you to Amir Omidi, Chris Palmer, Colm MacCárthaigh, David Adrian, David Anderson, Jeff Hodges, Matt Green, Matthew McPherrin, Paul Kehrer, Ryan Sleevi, and Soatok for contributing to the bounty.&lt;/p&gt;
&lt;p&gt;This helps fill out a page of cryptographic history, and reinforces our confidence in these critical cryptographic primitives.&lt;/p&gt;
&lt;p&gt;The NIST elliptic curves (P-192, P-224, P-256, P-384, and P-521) were published by NIST in FIPS 186-2 in 2000. They were generated at random according to ANSI X9.62 by taking an arbitrary seed, hashing it with SHA-1, and using the output to derive some of the parameters. Until this week, we had no idea how those arbitrary seeds were picked.&lt;/p&gt;
&lt;p&gt;The lore was that Dr. Jerry Solinas, who passed away in early 2023, generated them in 1997 by hashing some English sentences that he later forgot. You can read more in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://saweis.net/posts/nist-curve-seed-origins.html&quot;&gt;this article by Steve Weis&lt;/a&gt; or in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/seeds-bounty/&quot;&gt;the original bounty announcement&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Turns out memory can play tricks on everyone: we now know they were instead generated with the POSIX-standard &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://man.archlinux.org/man/drand48.3.en&quot;&gt;drand48&lt;/a&gt; random number generator.&lt;/p&gt;
&lt;p&gt;The algorithm is as simple as passing the then-current UNIX timestamp to &lt;code&gt;srand48()&lt;/code&gt; and then producing one byte at a time with drand48. Each sequence of 20 bytes is tried as a seed following ANSI X9.62 and rejected if the resulting curve is not valid. Some timestamps are used to generate multiple curves, by continuing to try seeds after accepting the first one. I &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/FiloSottile/mostly-harmless/blob/main/seeds/seeds.go&quot;&gt;reimplemented the algorithm in Go&lt;/a&gt; to verify the claim. Read more in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.anthropic.com/research/ecdsa-curve-origins&quot;&gt;Anthropic’s writeup&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are two additional undocumented criteria beyond the ANSI X9.62 ones, one fairly clear and one that still escapes us.&lt;/p&gt;
&lt;p&gt;If the bit length of the order &lt;em&gt;n&lt;/em&gt; of the resulting curve is more than the bit length of the base field &lt;em&gt;p&lt;/em&gt; of the curve, the seed is rejected. (The condition is not the stricter &lt;em&gt;n&lt;/em&gt; &amp;lt; &lt;em&gt;p&lt;/em&gt;, because prime239v2 has &lt;em&gt;p&lt;/em&gt; &amp;lt; &lt;em&gt;n&lt;/em&gt; &amp;lt; 2²³⁹.)&lt;/p&gt;
&lt;p&gt;This leaves us with roughly twice as many valid seeds as were published. We explored a number of explanations for this last bit, and found no good fit. Maybe the same RNG was used to validate seeds, consuming RNG output and skipping some potential candidates, although the “gaps” are not regular. Maybe there was just a bug in one of the algorithms that returned the wrong result half the time; these calculations, which we can now do with off-the-shelf software in minutes, were still very much state-of-the-art in 1997.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot;&gt;srand48(863711343) [1997-05-15 15:49:03 +0000 UTC]:
  accepted   36th seed 25e5[...]: not published
  accepted 1562nd seed d09b[...]: not published
  accepted 2185th seed e43b[...]: prime239v1
  accepted 2320th seed e8b4[...]: prime239v2
  accepted 2491st seed 1256[...]: not published
  accepted 2682nd seed d2c1[...]: not published
  accepted 2863rd seed 3a70[...]: not published
  accepted 3048th seed 7d73[...]: prime239v3
srand48(863966734) [1997-05-18 14:45:34 +0000 UTC]:
  accepted  139th seed 3045[...]: P-192 a.k.a. prime192v1
  accepted 2198th seed 692d[...]: not published
  accepted 5228th seed c469[...]: prime192v3
  accepted 5405th seed 3ad1[...]: not published
  accepted 6013th seed 31a9[...]: prime192v2
srand48(871002724) [1997-08-08 01:12:04 +0000 UTC]:
  accepted 1136th seed d09e[...]: P-521
srand48(871696354) [1997-08-16 01:52:34 +0000 UTC]:
  accepted  876th seed 50c7[...]: not published
  accepted 1842nd seed c49d[...]: P-256 a.k.a. prime256v1
srand48(872952608) [1997-08-30 14:50:08 +0000 UTC]:
  accepted 2688th seed a335[...]: P-384
srand48(891607927) [1998-04-03 12:52:07 +0000 UTC]:
  accepted  777th seed d007[...]: not published
  accepted 1485th seed 18cf[...]: not published
  accepted 5736th seed 3c17[...]: not published
  accepted 7026th seed bd71[...]: P-224
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What does this mean for the security of elliptic curve cryptography based on the NIST curves? Well, even before this discovery there were no serious concerns among practitioners. In Section 3.1 of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://eprint.iacr.org/2015/1018&quot;&gt;their 2015 paper “A riddle wrapped in an enigma,”&lt;/a&gt; Koblitz and Menezes make a convincing argument that it’s implausible that the NSA could have selected a seed to exploit a weakness that escaped more than 15 (now 25) years of cryptanalysis.&lt;/p&gt;
&lt;p&gt;However, as reported in a later version of that paper, Daniel Bernstein and Tanja Lange described to Koblitz and Menezes in 2017 a technique that could have exploited 70 bits of freedom in selection of the seed to target a hypothetical class of 2⁶¹ weak prime-order curves. (Bernstein is the author of a competing elliptic curve design, Curve25519.) This contributed to enduring conspiracy theories, which had significant appeal among the non-specialist public (although many have since moved on to other, adjacent, conspiracy theories). All this speculation harmed confidence in robust cryptographic systems and was part of my motivation for the bounty.&lt;/p&gt;
&lt;p&gt;This week’s findings completely rule out the technique described by Bernstein and Lange. Even if the NSA had been free to pick any timestamp in 1997–98 and any of the first 16 prime-order candidates (which generously accounts for the unexplained rejections), they would have had barely 30 bits of freedom. Not enough to intentionally target a hypothetical small class of weak curves, not enough to embed any kind of &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://en.wikipedia.org/wiki/NOBUS&quot;&gt;NOBUS&lt;/a&gt; backdoor.&lt;/p&gt;
&lt;p&gt;Along with &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.rfc-editor.org/rfc/rfc6979&quot;&gt;deterministic&lt;/a&gt; and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.ietf.org/archive/id/draft-irtf-cfrg-det-sigs-with-noise-05.html&quot;&gt;hedged&lt;/a&gt; ECDSA signing algorithms, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/mit-plv/fiat-crypto&quot;&gt;formally verified field implementations&lt;/a&gt;, and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://eprint.iacr.org/2015/1060&quot;&gt;the 2015 complete addition formulas by Renes, Costello, and Batina&lt;/a&gt;, this nicely completes the “rehabilitation arc” of NIST elliptic curve cryptography, which I would now personally recommend over Curve25519/Ed25519/X25519.&lt;/p&gt;
&lt;p&gt;Just in time for them to be retired in favor of post-quantum signatures.&lt;/p&gt;
&lt;p&gt;P.S. The Anthropic team also figured out how the seeds of the NIST B-curves (the ones over binary fields) were generated, but no one uses those, so who cares.&lt;/p&gt;
&lt;h2&gt;The picture&lt;/h2&gt;
&lt;p&gt;In a FamilyMart in Taiwan I found this delightful dice set. A full roll carries log₂(6²⁰) ≈ 51.7 bits of entropy. Rolling them twice can make a pretty robust cryptographic key!&lt;/p&gt;
&lt;p&gt;I thought it would be funny to label them as Geomys FIPS 140-3 Entropy Sources (which of course they are not, though we do have one of those) and hand them out as swag. However, I found no way to order them online, so I simply visited over a dozen FamilyMarts and brought home 50 packs.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;article-picture article-picture-fallback&quot; style=&quot;--image-ratio:16 / 9&quot;&gt;&lt;img src=&quot;https://assets.buttondown.email/images/fc811646-65b3-4c41-9a2c-ff68584f75b5.jpeg?w=960&amp;amp;fit=max&quot; alt=&quot;A set of 20 small dice in the evening light. They are white with red and black etchings. A small clear plastic box is next to them.&quot; loading=&quot;eager&quot; decoding=&quot;async&quot; fetchpriority=&quot;high&quot; referrerpolicy=&quot;no-referrer&quot; class=&quot;progressive-image&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Speaking of swag, I am 100% making stickers with the &lt;code&gt;srand48()&lt;/code&gt; seeds, and t-shirts for the Anthropic team and the contributors to the bounty. I accept suggestions for other seed-based swag. If you want to catch some, follow me on Bluesky at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://bsky.app/profile/filippo.abyssdomain.expert&quot;&gt;@filippo.abyssdomain.expert&lt;/a&gt; or on Mastodon at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://abyssdomain.expert/@filippo&quot;&gt;@filippo@abyssdomain.expert&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <id>urn:aggr:item:64bbb9c12a49e7ef9b465d98710fdcfe16489daf</id>
    <title type="text">(Post-Quantum) Cryptographic Right Answers</title>
    <link rel="alternate" type="text/html" href="https://aggr.aymericbeaumet.com/items/lobste-rs/2026-10-12-post-quantum-cryptographic-right-answers/"/>
    <link rel="via" type="text/html" href="https://words.filippo.io/right-answers/"/>
    <published>2026-10-12T13:44:38+00:00</published>
    <updated>2026-10-12T13:44:38+00:00</updated>
    <author>
      <name>words.filippo.io via jtdowney</name>
    </author>
    <category term="news"/>
    <category term="cryptography"/>
    <source>
      <title>Lobsters: Top Stories of the Past Week</title>
    </source>
    <summary type="text">Comments</summary>
    <content type="html">&lt;p&gt;Publishing “Cryptographic Right Answers” for developers is a tradition by now. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html&quot;&gt;Colin Percival started in 2009&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://gist.github.com/tqbf/be58d2d39690c3b366ad&quot;&gt;Thomas Ptacek followed up in 2015&lt;/a&gt;, and then &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.latacora.com/blog/cryptographic-right-answers/&quot;&gt;Latacora carried on in 2018&lt;/a&gt; and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.latacora.com/blog/post-quantum-cryptographic-right-answers/#pqc-right-answers&quot;&gt;2024&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It might feel too soon for an update, but &lt;em&gt;so much has changed&lt;/em&gt;. The timeline of the post-quantum transition &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/crqc-timeline/&quot;&gt;dramatically shifted in early 2026&lt;/a&gt;, and there is now consensus that it needs to complete by 2029. 2029! We have three years!&lt;/p&gt;
&lt;p&gt;The good news is that, despite what you might have heard, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/128-bits/&quot;&gt;symmetric cryptography is going to be fine, because Grover does not matter&lt;/a&gt;. The bad news is that all asymmetric answers changed compared to 2018 and most even compared to 2024.&lt;/p&gt;
&lt;p&gt;The answers below assume you are not constrained by any specific compliance regime, although they are mostly compatible with FIPS 140.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-1&quot; id=&quot;fnref-1&quot; data-footnote-ref&gt;1&lt;/a&gt;&lt;/sup&gt; If you need to follow something stricter like CNSA 2.0, you’ll know. Just substitute as appropriate.&lt;/p&gt;
&lt;p&gt;Finally, remember the other big change of 2026: LLMs are very good at finding bugs now. “Get a review from the best model you can afford” is now firmly a Cryptographic Right Answer, along with the long-time classic “use a memory-safe language.”&lt;/p&gt;
&lt;p&gt;If you’re in a rush, here’s the cheat sheet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Encrypting data&lt;/strong&gt;: If you’re in the cloud, KMS. Otherwise, XAES-256-GCM or XChaCha20Poly1305 with random nonces. If you’re encrypting more than a few MB, use Cobblestone-128.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Symmetric key length&lt;/strong&gt;: Don’t stress it. 256-bit keys if you need to pick.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Symmetric “signatures”&lt;/strong&gt;: HMAC-SHA-256.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hash algorithm&lt;/strong&gt;: SHA-2 is still fine, but use a real multihash (SequenceHash) if needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Random IDs&lt;/strong&gt;: 32 random bytes, a.k.a. 256-bit random numbers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password handling&lt;/strong&gt;: Argon2id or scrypt. But have you considered passkeys instead?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key exchange&lt;/strong&gt;: X25519+ML-KEM-768 in a secure, standard protocol.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asymmetric encryption&lt;/strong&gt;: HPKE with (MLKEM768-X25519 a.k.a. X-Wing, HKDF-SHA256, AES-128-GCM), or age with PQ keys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Asymmetric signatures&lt;/strong&gt;: ML-DSA-44.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Website or client-server application security&lt;/strong&gt;: If you’re in the cloud, use its ALB/NLB. Otherwise, rustls, Go’s crypto/tls, BoringSSL, AWS-LC, or s2n-tls with a popular, updated ACME client.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Online backups&lt;/strong&gt;: restic with a long random password.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now let’s dive in.&lt;/p&gt;
&lt;h3&gt;Encrypting data&lt;/h3&gt;
&lt;p&gt;If you’re in the cloud, &lt;strong&gt;KMS&lt;/strong&gt;. Otherwise, &lt;strong&gt;XAES-256-GCM&lt;/strong&gt; or &lt;strong&gt;XChaCha20Poly1305&lt;/strong&gt; with random nonces. If you’re encrypting more than a few MB, use &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://c2sp.org/chunked-encryption&quot;&gt;&lt;strong&gt;Cobblestone-128&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: AES-CTR with HMAC.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: NaCl/libsodium default, or ChaCha20Poly1305, or AES-GCM.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: KMS, or XSalsa20+Poly1305.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: KMS, or NaCl’s secretbox, or XChaCha20Poly1305.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.latacora.com/blog/cryptographic-right-answers/#encrypting-data&quot;&gt;Latacora’s reasoning for why and when you’d use your cloud’s KMS&lt;/a&gt; is still solid, so start there.&lt;/p&gt;
&lt;p&gt;If you’re encrypting locally, you want an AEAD where you can avoid worrying about tracking nonces or how many messages you encrypted, so you want random nonces of at least 128 bits.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-2&quot; id=&quot;fnref-2&quot; data-footnote-ref&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;NaCl/libsodium’s secretbox is just a &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/nacl-api/&quot;&gt;paper-thin wrapper&lt;/a&gt; around a less common AE construction (XSalsa20+Poly1305), so instead I recommend &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/xaes-256-gcm/&quot;&gt;XAES-256-GCM&lt;/a&gt; or XChaCha20Poly1305. The former is an efficient composition of a standard KDF and AES-GCM, packaged as an extended-nonce AEAD, so even if your library doesn’t provide it under that name you can assemble it from the KDF and AES-GCM APIs.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-3&quot; id=&quot;fnref-3&quot; data-footnote-ref&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Regular AES-GCM and ChaCha20Poly1305 with random nonces are only ok if you &lt;em&gt;know for certain&lt;/em&gt; you won’t encrypt more than a billion messages with one key. AES-GCM-SIV is also a good AEAD choice if you happen to have it everywhere you need it, but that’s kinda unlikely. Some even newer options have nice properties like key commitment, but are not commonly available yet.&lt;/p&gt;
&lt;p&gt;Since you have an AEAD, consider putting context in the additional data, like the user ID you’re encrypting for, or the version of the protocol. Anything not secret that you will definitely still know at decrypt time.&lt;/p&gt;
&lt;p&gt;Very important is how you handle large messages, which KMS will not solve for you. &lt;em&gt;Do not&lt;/em&gt; pass one large message to an AEAD, or you might be forced to process or store the plaintext before having authenticated it at decrypt time. &lt;em&gt;Absolutely do not&lt;/em&gt; use unauthenticated stream ciphers. Instead, you should use STREAM (which is not a stream cipher but a way to use AEADs on a stream… I know, I know, terrible name). There are a lot of options—too many, in fact—and shamefully none are the consensus answer. &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developers.google.com/tink/encrypt-large-files-or-data-streams#streaming_aead&quot;&gt;Tink has one&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://libsodium.gitbook.io/doc/secret-key_cryptography/secretstream&quot;&gt;libsodium has one&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/aws/aws-encryption-sdk-python/blob/a6c0413bb0ddc9bccdefad9f1d2c031e0bc4db29/examples/src/file_streaming_example.py&quot;&gt;the AWS Encryption SDK has one&lt;/a&gt;, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://c2sp.org/FLOE&quot;&gt;FLOE is one&lt;/a&gt;, age has one, and the list goes on. I recommend &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://c2sp.org/chunked-encryption&quot;&gt;Cobblestone-128&lt;/a&gt;, which is a STREAM variant derived from TLS, and is available in pyca/cryptography and soon in the Go standard library (and in the meantime at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://filippo.io/cobblestone&quot;&gt;filippo.io/cobblestone&lt;/a&gt;).&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-4&quot; id=&quot;fnref-4&quot; data-footnote-ref&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: unauthenticated modes (CBC, CTR, raw stream ciphers), random 96-bit nonces unless you spent a lot of time thinking about how many messages you encrypt, rolling your own AEAD construction.&lt;/p&gt;
&lt;h3&gt;Symmetric key length&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Don’t stress it.&lt;/strong&gt; 256-bit keys if you need to pick.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: 256-bit AES keys.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: 256-bit keys.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: 256-bit keys.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: 256-bit keys.&lt;/p&gt;
&lt;p&gt;Remember that Grover doesn’t actually matter and 128-bit brute-force attacks don’t exist. Still, 256-bit keys rule out multi-target attacks, which is nice.&lt;/p&gt;
&lt;p&gt;However, don’t make other decisions based on this. For example, the recommended Cobblestone suite uses 128-bit keys and that’s fine because it has a careful analysis of multi-target attacks. Likewise, don’t go changing the default cipher suite preferences of your TLS library to enforce 256-bit keys; there’s no need.&lt;/p&gt;
&lt;p&gt;AES-256 is slower than AES-128 because unfortunately Rijndael ties the number of rounds to the key size, but this probably won’t matter in practice.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: anything other than 128 or 256 bits, worrying too much about size, keys that are not uniform (e.g. using a password or ASCII hex string directly as a key).&lt;/p&gt;
&lt;h3&gt;Symmetric “signatures”&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;HMAC-SHA-256&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: HMAC.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: HMAC.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: HMAC.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: HMAC.&lt;/p&gt;
&lt;p&gt;Yay for HMAC.&lt;/p&gt;
&lt;p&gt;Just be careful to sidestep canonicalization and parser alignment bugs by always authenticating a verbatim string of bytes, not something you need to recompute.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Good:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;JSON&quot;&gt;&lt;span class=&quot;syntax-source syntax-json&quot;&gt;&lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json&quot;&gt;&lt;span class=&quot;syntax-string syntax-quoted syntax-double syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;inner&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-end syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-string syntax-quoted syntax-double syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;{&lt;span class=&quot;syntax-constant syntax-character syntax-escape syntax-json&quot;&gt;\&amp;quot;&lt;/span&gt;foo&lt;span class=&quot;syntax-constant syntax-character syntax-escape syntax-json&quot;&gt;\&amp;quot;&lt;/span&gt;:123}&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-end syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json&quot;&gt;&lt;span class=&quot;syntax-string syntax-quoted syntax-double syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;hmac&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-end syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-string syntax-quoted syntax-double syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;MbPsZq7QGCllJgDnSSy/cyM10HjqiVu35UO7yyY5d3w=&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-end syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the HMAC and then process or store or forward just &lt;code&gt;{&amp;quot;foo&amp;quot;:123}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bad:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;code-snippet&quot; data-language=&quot;JSON&quot;&gt;&lt;span class=&quot;syntax-source syntax-json&quot;&gt;&lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-dictionary syntax-begin syntax-json&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json&quot;&gt;&lt;span class=&quot;syntax-string syntax-quoted syntax-double syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;foo&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-end syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-constant syntax-numeric syntax-json&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-dictionary syntax-pair syntax-json&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-key syntax-json&quot;&gt;&lt;span class=&quot;syntax-string syntax-quoted syntax-double syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;hmac&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-end syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;syntax-meta syntax-structure syntax-dictionary syntax-value syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-separator syntax-dictionary syntax-key-value syntax-json&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;syntax-string syntax-quoted syntax-double syntax-json&quot;&gt;&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-begin syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;MbPsZq7QGCllJgDnSSy/cyM10HjqiVu35UO7yyY5d3w=&lt;span class=&quot;syntax-punctuation syntax-definition syntax-string syntax-end syntax-json&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class=&quot;syntax-punctuation syntax-section syntax-dictionary syntax-end syntax-json&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use KMAC or keyed BLAKE3 if you already had a reason to use their underlying hashes. HMAC-SHA-512 is also fine.&lt;/p&gt;
&lt;p&gt;Remember to compare tags with dedicated constant-time functions.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: rolling your own &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://blog.trailofbits.com/2024/08/21/yolo-is-not-a-valid-hash-construction/#yolomac&quot;&gt;YoloMAC&lt;/a&gt;, encrypted hashes, standalone one-time MACs like Poly1305 or GMAC.&lt;/p&gt;
&lt;h3&gt;Hash algorithm&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SHA-2&lt;/strong&gt; is still fine, but use a real multihash if needed.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: SHA-256 (SHA-2), plan on SHA-3.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: SHA-2.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: SHA-2.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: SHA-2.&lt;/p&gt;
&lt;p&gt;The best option, if you have it available, is still SHA-512/256, which isn’t vulnerable to length-extension attacks. Otherwise, SHA-256 and SHA-512 are both fine.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-5&quot; id=&quot;fnref-5&quot; data-footnote-ref&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;SHA-3 is unnecessary (because SHA-2 is fine) and slow,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-6&quot; id=&quot;fnref-6&quot; data-footnote-ref&gt;6&lt;/a&gt;&lt;/sup&gt; but not &lt;em&gt;wrong&lt;/em&gt;. If you already have Keccak (SHA-3) around for ML-KEM and ML-DSA, and you want to use it for hashing, draw 32 bytes from SHAKE128.&lt;/p&gt;
&lt;p&gt;BLAKE2 is a weird choice these days. BLAKE3 is very fast, but complex and uncommon enough that you want to have a good reason to use it and a good reason to trust your implementation.&lt;/p&gt;
&lt;p&gt;Multihashing is when you need to hash multiple &lt;em&gt;things&lt;/em&gt; at once. For example, multiple columns of a row, or a filename along with the file contents. It’s subtler than it sounds, so use &lt;strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://blog.trailofbits.com/2026/10/02/sequencehash-multihashing-for-the-rest-of-us/&quot;&gt;SequenceHash&lt;/a&gt;&lt;/strong&gt; with SHA-2, or TupleHash (SP 800-185) with SHA-3.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: SHA-1, MD5, all other hash functions, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://blog.trailofbits.com/2024/08/21/yolo-is-not-a-valid-hash-construction/#yolomultihash&quot;&gt;rolling your own multihash&lt;/a&gt;, truncating to less than 256 bits unless a cryptographer told you collisions are not a concern.&lt;/p&gt;
&lt;h3&gt;Random IDs&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;32 random bytes&lt;/strong&gt;, a.k.a. 256-bit random numbers.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: 256-bit random numbers.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: 256-bit random numbers.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: 256-bit random numbers.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: 256-bit random numbers.&lt;/p&gt;
&lt;p&gt;Note that UUIDv4 and APIs like &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://pkg.go.dev/crypto/rand#Text&quot;&gt;rand.Text&lt;/a&gt; will give you ~128-bit values instead. That’s usually fine, unless you need more than 280 trillion (2⁴⁸) unique values. 256 bits is overkill, but why not.&lt;/p&gt;
&lt;p&gt;Use your operating system’s CSPRNG: &lt;code&gt;getrandom&lt;/code&gt; on Linux, &lt;code&gt;ProcessPrng&lt;/code&gt; on Windows, &lt;code&gt;arc4random_buf&lt;/code&gt; on macOS and BSD, &lt;code&gt;getRandomValues&lt;/code&gt; on the web. A good library will wrap these for you.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: non-cryptographic PRNGs, userspace random number generators like OpenSSL’s, haveged, CPU jitter entropy.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-7&quot; id=&quot;fnref-7&quot; data-footnote-ref&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3&gt;Password handling&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Argon2id&lt;/strong&gt; or &lt;strong&gt;scrypt&lt;/strong&gt;. But have you considered &lt;strong&gt;passkeys&lt;/strong&gt; instead?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: scrypt or PBKDF2.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: scrypt, then bcrypt, then PBKDF2 if you must.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: scrypt, then Argon2, then bcrypt, then PBKDF2 if you must.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: Argon2id, then scrypt, then bcrypt, then PBKDF2 if you must.&lt;/p&gt;
&lt;p&gt;Everything my predecessors said is still spot on: “you can throw a dart at a wall to pick one of these.” I dropped bcrypt because the 72-byte truncation &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://josephscott.org/blog/2024/11/okta-bcrypt-and-52-character-usernames/&quot;&gt;bites&lt;/a&gt;. One tip: you could argue to your auditors that scrypt is FIPS 140 compliant because its actual KDF step is PBKDF2, which is what they want. I heard it works.&lt;/p&gt;
&lt;p&gt;For parameters, ignore RFC 9106 which apparently wants to murder your production system, and use &lt;code&gt;m=64 MiB, t=2, p=1&lt;/code&gt; for Argon2id (libsodium’s INTERACTIVE preset) or &lt;code&gt;N=2^16, r=8, p=1&lt;/code&gt; for scrypt. They use the same memory, but the scrypt ones will take ~1.5× the CPU time and ¼ the GPU time, which is why you should prefer Argon2id. If you’re doing non-interactive key derivation, feel free to crank up Argon2id’s &lt;em&gt;m&lt;/em&gt;, and then &lt;em&gt;t&lt;/em&gt; (or just &lt;em&gt;N&lt;/em&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-8&quot; id=&quot;fnref-8&quot; data-footnote-ref&gt;8&lt;/a&gt;&lt;/sup&gt; for scrypt) as far as tolerable.&lt;/p&gt;
&lt;p&gt;Much more importantly, do you really need passwords? If you only need them for logins, could you use phishing-proof passkeys instead? Maybe with &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://rmondello.com/2025/01/02/magic-links-and-passkeys/&quot;&gt;email magic links as a fallback&lt;/a&gt;? Or just support SSO (as long as it’s OIDC and not SAML)?&lt;/p&gt;
&lt;p&gt;(Note that in 2026, passkeys are still not PQ. That’s mostly fine. They’re still phishing-proof and credential-stuffing-proof. All a quantum attacker can do is log in to a specific site if they obtain its database or intercept a login.)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: regular hashes, spending time thinking about password hashes instead of focusing on passkeys, phishable variants of email-based logins.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-9&quot; id=&quot;fnref-9&quot; data-footnote-ref&gt;9&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3&gt;Key exchange&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;X25519+ML-KEM-768&lt;/strong&gt; in a secure, standard protocol.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: 2048-bit Group #14 with a generator of 2.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: Probably still DH-2048, or NaCl.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: Probably nothing. Or Curve25519.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: X25519+ML-KEM-768.&lt;/p&gt;
&lt;p&gt;The 2018 version of the Latacora answers is correct in asking “why are you as a developer thinking about this, and what does it even mean?!” Don’t roll your own key exchange protocol. Just pick X25519MLKEM768 in TLS, mlkem768x25519-sha256 in SSH, and/or MLKEM768-X25519 in HPKE and age. If you really need to obtain some keys and you know what you’re doing, both TLS and HPKE have “exporters” to derive them.&lt;/p&gt;
&lt;p&gt;Non-PQ solutions are an active, urgent liability due to harvest-now-decrypt-later attacks, and PQ alternatives range from impractical (HQC-KEM, FrodoKEM) to impractical and broken (&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/2kfyUnLObWQ&quot;&gt;Classic McEliece&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;For key exchange, using an ECC hybrid is cheap enough to be worth it, and indeed ML-KEM is usually deployed alongside ECDH. However, if picking or implementing the hybrid is going to make you stay even a single month longer on a classical, non-PQ key exchange, go straight to pure ML-KEM-768 now.&lt;/p&gt;
&lt;p&gt;P-256+ML-KEM-768 is also fine, just less common. NIST P curves got a bad rep for many years, but I think it’s 90% because they came out before we knew how to do cryptography engineering particularly well (and because of… let’s say non-&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://en.wikipedia.org/wiki/Wikipedia:Neutral_point_of_view&quot;&gt;NPOV&lt;/a&gt; proselytizing). Almost none of the attacks against ECDH on NIST P curves even matter for ephemeral key exchanges.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: anything and everything without ML-KEM or X-Wing (a specific X25519+ML-KEM-768 combiner) in its name.&lt;/p&gt;
&lt;h3&gt;Asymmetric encryption&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;HPKE with (MLKEM768-X25519 a.k.a. X-Wing, HKDF-SHA256, AES-128-GCM)&lt;/strong&gt;, or &lt;strong&gt;age with PQ keys&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: RSAES-OAEP with SHA-256, MGF1+SHA-256, and exponent 65537.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: NaCl.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: NaCl/libsodium (box / crypto_box).&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: Tricky, wait for established PQ solutions.&lt;/p&gt;
&lt;p&gt;This is one of the most improved answers in recent years, as we now have a standard to do hybrid encryption: HPKE. (“Hybrid” as in asymmetric + symmetric, not as in classical + post-quantum, yay for overloaded names.)&lt;/p&gt;
&lt;p&gt;MLKEM768-P256 is also ok as a choice of KEM, but less available. Pure ML-KEM-768 is fine if it’s all you have. All KDF and AEAD choices are fine, as well.&lt;/p&gt;
&lt;p&gt;If your message is large, do the same chunking Cobblestone does, with multiple sequential HPKE Seal/Open calls in place of the AEAD.&lt;/p&gt;
&lt;p&gt;Note that the PQ HPKE KEMs are maddeningly still a draft.&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-10&quot; id=&quot;fnref-10&quot; data-footnote-ref&gt;10&lt;/a&gt;&lt;/sup&gt; Worse, they are unnecessarily spread over four drafts, such that I had to write &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://filippo.io/hpke-pq&quot;&gt;filippo.io/hpke-pq&lt;/a&gt;. However, I made the authors solemnly swear they will not change the wire format, and then we all shipped them in our cryptographic libraries, so you can and should use them.&lt;/p&gt;
&lt;p&gt;If you need something higher-level, with multiple recipients or a CLI, use age&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-11&quot; id=&quot;fnref-11&quot; data-footnote-ref&gt;11&lt;/a&gt;&lt;/sup&gt; with its new &lt;code&gt;age1pq&lt;/code&gt;-prefixed keys, based on MLKEM768-X25519. PQ support is also rolling out for plugins like the YubiKey and the Secure Enclave ones.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: again anything not based on ML-KEM (RSA and NaCl’s box&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-12&quot; id=&quot;fnref-12&quot; data-footnote-ref&gt;12&lt;/a&gt;&lt;/sup&gt; in particular), OpenPGP, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.latacora.com/blog/2020/02/19/stop-using-encrypted-email/&quot;&gt;encrypting emails&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Asymmetric signatures&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ML-DSA-44&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: RSASSA-PSS with SHA-256, MGF1+SHA-256, and exponent 65537.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: NaCl, Ed25519, or RFC 6979.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: NaCl or Ed25519.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: Ed25519 if short-lived, otherwise Ed25519+ML-DSA-65.&lt;/p&gt;
&lt;p&gt;In January 2026, I would have told you to wait and see, and then &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/crqc-timeline/&quot;&gt;everything started moving a lot faster&lt;/a&gt;. You need to move to PQ for auth, too.&lt;/p&gt;
&lt;p&gt;Just use ML-DSA-44. If your message needs pre-hashing, use “external-μ (Mu)” signing, not HashML-DSA. Store keys as 32-byte seeds exclusively.&lt;/p&gt;
&lt;p&gt;The two PQ alternatives are FN-DSA, which is stuck somewhere in the US Department of Commerce, and SLH-DSA, which is based on hash functions. The latter can make sense for some specialist use cases, but is slow, large, and not widely available in implementations.&lt;/p&gt;
&lt;p&gt;Why not a hybrid or “composites”? Hybrid auth is &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mailarchive.ietf.org/arch/msg/tls/CI9HgxA2UKBIyNj0xNSumH3Qogg/&quot;&gt;more complex&lt;/a&gt; and less useful than hybrid key exchange. I need to write a long-form post about this, but in short either you already have a key rotation strategy that you can use to rotate back to RSA/ECDSA/Ed25519 in the unlikely case of a cryptanalytic break, or you have such a long time horizon that you should assume quantum computers will break the classical half anyway.&lt;/p&gt;
&lt;p&gt;Also, ML-DSA is just such a better signature algorithm than its predecessors. It has hedged randomness (which is better than both random and deterministic nonces), built-in context, extra properties like SUF-CMA, and is relatively simple to implement and test.&lt;/p&gt;
&lt;p&gt;Why the -44 parameter set, when I’m recommending ML-KEM-768? Unlike ML-KEM-512 (NIST category 1), ML-DSA-44 sits at NIST category 2, with some margin above the 128-bit security level already.&lt;/p&gt;
&lt;p&gt;The one exception to the no-composites rule is SSH keys. OpenSSH 10.6 picked &lt;code&gt;ssh-mldsa44-ed25519&lt;/code&gt;. I think they &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://mailarchive.ietf.org/arch/msg/ssh/4sb0aPTWeX4KA3GgK77ns17UHrU/&quot;&gt;made a mistake&lt;/a&gt;, but it would be a mistake for you to go off the beaten path there.&lt;/p&gt;
&lt;p&gt;Note that asymmetric signatures are overused, and especially now that they are not cheap, you should try to avoid them. Consider whether you could use a simple HMAC. Maybe you can replace signed JWTs with random session tokens. If instead you do want to commit to something publicly, consider whether you should use &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/transparency-dev/tessera&quot;&gt;a transparency log&lt;/a&gt;, and get a more practically useful notion of non-repudiability (and potentially smaller sizes, if you amortize the signature over multiple proofs).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: all non-PQ options like RSA/ECDSA/Ed25519, stateful signatures (LMS/XMSS), HashML-DSA, ML-DSA “&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://cs.opensource.google/go/go/+/master:src/crypto/internal/fips140/mldsa/semiexpanded.go;l=13-22;drc=c888fd67f00f77446e563f90e3307de7b6b3a910&quot;&gt;semi-expanded keys&lt;/a&gt;,” composite signatures, OpenPGP.&lt;/p&gt;
&lt;h3&gt;Website or client-server application security&lt;/h3&gt;
&lt;p&gt;If you’re in the cloud, use its &lt;strong&gt;ALB/NLB&lt;/strong&gt;. Otherwise, &lt;strong&gt;rustls&lt;/strong&gt;, &lt;strong&gt;Go’s crypto/tls&lt;/strong&gt;, &lt;strong&gt;BoringSSL&lt;/strong&gt;, &lt;strong&gt;AWS-LC&lt;/strong&gt;, or &lt;strong&gt;s2n-tls&lt;/strong&gt; with a &lt;strong&gt;popular, updated ACME client&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: OpenSSL for websites, custom RSA protocol for applications.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: OpenSSL, or BoringSSL if you can. Or just AWS ELBs.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: AWS ALB/ELB or OpenSSL, with Let’s Encrypt.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: AWS ALB/ELB or OpenSSL, with Let’s Encrypt.&lt;/p&gt;
&lt;p&gt;Not recommending OpenSSL will probably be my least practical suggestion, but OpenSSL has been making consistently puzzling and problematic choices for years now. The pyca/cryptography authors, in &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://cryptography.io/en/latest/statements/state-of-openssl/&quot;&gt;a long post about OpenSSL’s issues&lt;/a&gt;, summarize it as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Our experience with OpenSSL has been on a negative trajectory for several years.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They even &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.youtube.com/watch?v=RUIguklWwx0&quot;&gt;presented at the 2025 OpenSSL Conference&lt;/a&gt; (!?), apparently fruitlessly. They’re not alone in criticizing OpenSSL: here are &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://cdn.haproxy.com/blog/state-of-ssl-stacks&quot;&gt;HAProxy&lt;/a&gt; from the server side, and &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://daniel.haxx.se/blog/2025/06/23/a-family-of-forks/&quot;&gt;curl&lt;/a&gt; from the client side.&lt;/p&gt;
&lt;p&gt;I recommend rustls or Go’s crypto/tls,&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-13&quot; id=&quot;fnref-13&quot; data-footnote-ref&gt;13&lt;/a&gt;&lt;/sup&gt; which are memory safe. If you need to stick to C, both BoringSSL and AWS-LC are very competently maintained OpenSSL forks, and the latter, unlike BoringSSL, has versioned releases. s2n-tls and rustls-ffi are interesting options if you don’t need libssl compatibility.&lt;/p&gt;
&lt;p&gt;Whatever you use, make sure it prioritizes the X25519MLKEM768 key exchange, which requires TLS 1.3. Your cloud load balancer might require you to select a PQ security policy. On RHEL 9 and derivatives, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/security_hardening/using-the-system-wide-cryptographic-policies_security-hardening#post_quantum_cryptography&quot;&gt;you need to opt in&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Previous lists split websites and client-server applications. I merged them because these days you’ll just use the same TLS stack for both. What changes is the PKI. For applications where you control both ends, consider making your own ML-DSA-44 X.509 certificates, self-signed or from a private root. For websites, wire up a &lt;em&gt;popular&lt;/em&gt;, well-supported ACME client and keep it up to date. Merkle Tree Certificates will roll out in 2027, and you want to be well positioned to be included in the transition.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Avoid&lt;/em&gt;: rolling your own transport protocol, TLS libraries that failed at their core job before (GnuTLS&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-14&quot; id=&quot;fnref-14&quot; data-footnote-ref&gt;14&lt;/a&gt;&lt;/sup&gt;, Mbed TLS&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-15&quot; id=&quot;fnref-15&quot; data-footnote-ref&gt;15&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-16&quot; id=&quot;fnref-16&quot; data-footnote-ref&gt;16&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-17&quot; id=&quot;fnref-17&quot; data-footnote-ref&gt;17&lt;/a&gt;&lt;/sup&gt;, wolfSSL&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-18&quot; id=&quot;fnref-18&quot; data-footnote-ref&gt;18&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-19&quot; id=&quot;fnref-19&quot; data-footnote-ref&gt;19&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-20&quot; id=&quot;fnref-20&quot; data-footnote-ref&gt;20&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-21&quot; id=&quot;fnref-21&quot; data-footnote-ref&gt;21&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-22&quot; id=&quot;fnref-22&quot; data-footnote-ref&gt;22&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-23&quot; id=&quot;fnref-23&quot; data-footnote-ref&gt;23&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-24&quot; id=&quot;fnref-24&quot; data-footnote-ref&gt;24&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-25&quot; id=&quot;fnref-25&quot; data-footnote-ref&gt;25&lt;/a&gt;&lt;/sup&gt;, Botan&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-26&quot; id=&quot;fnref-26&quot; data-footnote-ref&gt;26&lt;/a&gt;&lt;/sup&gt; &lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;#fn-27&quot; id=&quot;fnref-27&quot; data-footnote-ref&gt;27&lt;/a&gt;&lt;/sup&gt;), offbeat TLS libraries, OpenSSL before version 3.5 (the first with X25519MLKEM768), any OpenSSL if you can, TLS 1.0–1.1, TLS 1.2 if you can, rolling your own ACME client.&lt;/p&gt;
&lt;h3&gt;Online backups&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;restic&lt;/strong&gt; with a long random password.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Percival, 2009&lt;/em&gt;: Tarsnap.&lt;br /&gt;
&lt;em&gt;Ptacek, 2015&lt;/em&gt;: Tarsnap.&lt;br /&gt;
&lt;em&gt;Latacora, 2018&lt;/em&gt;: Tarsnap.&lt;br /&gt;
&lt;em&gt;Latacora, 2024&lt;/em&gt;: Tarsnap.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/restic-cryptography/&quot;&gt;I checked restic’s encryption a very long time ago&lt;/a&gt; and it was slightly weird but good. Fully symmetric, so it’s not scared of quantum computers. Doesn’t look like anything changed since. If you need cloud storage, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.rsync.net/products/restic.html&quot;&gt;rsync.net has a “secret” plan for restic users&lt;/a&gt;, and they support &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.rsync.net/resources/notes/2025-q4-rsync.net_technotes.html&quot;&gt;an append-only mode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We all love Tarsnap, and I feel like I am betraying my elders right now… but its asymmetric cryptography layer is all RSA. &lt;em&gt;Technically&lt;/em&gt;, I think the only things that truly break are the lower-capability keys: a write-only key can decrypt archives and a read-only key can forge archives. Server authentication is HMAC, and the RSA public keys are never sent to the server. In that sense, Tarsnap is as secure as restic, which is an impressive feat for a 2008 design. However, this feels like a major footgun with impending quantum computers.&lt;/p&gt;
&lt;h2&gt;The picture&lt;/h2&gt;
&lt;p&gt;Kinda incredible that you can just walk up to this thing.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;article-picture article-picture-fallback&quot; style=&quot;--image-ratio:16 / 9&quot;&gt;&lt;img src=&quot;https://assets.buttondown.email/images/cc14aaa4-8914-48a4-9b5d-83f0ed53dd7a.jpeg?w=960&amp;amp;fit=max&quot; alt=&quot;The Colosseum, at night, pictured from the ground looking up.&quot; loading=&quot;eager&quot; decoding=&quot;async&quot; fetchpriority=&quot;high&quot; referrerpolicy=&quot;no-referrer&quot; class=&quot;progressive-image&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Follow me on Bluesky at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://bsky.app/profile/filippo.abyssdomain.expert&quot;&gt;@filippo.abyssdomain.expert&lt;/a&gt; or on Mastodon at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://abyssdomain.expert/@filippo&quot;&gt;@filippo@abyssdomain.expert&lt;/a&gt;.&lt;/p&gt;
&lt;section class=&quot;footnotes&quot; data-footnotes&gt;
&lt;ol&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;FIPS 140 still has some very ugly bits around module validation (runtime self-tests, the integrity check, and the worsening RNG mess) but the actual cryptography algorithms these days are “good, actually.” &lt;a href=&quot;#fnref-1&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;1&quot; aria-label=&quot;Back to reference 1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-2&quot;&gt;
&lt;p&gt;You might read that GCM can take 128-bit nonces, but when you do that it doesn’t have a separate CTR counter anymore, and messages with different GCM nonces can end up sharing CTR keystream, which makes the analysis more complicated. &lt;a href=&quot;#fnref-2&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;2&quot; aria-label=&quot;Back to reference 2&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-3&quot;&gt;
&lt;p&gt;Full disclosure: XAES-256-GCM is my design, but there are now multiple third-party implementations, including as part of Google’s Tink libraries, there’s &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://eprint.iacr.org/2025/878.pdf&quot;&gt;a security analysis by Amazon Web Services&lt;/a&gt;, and it’s &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://learn.microsoft.com/en-us/industry/healthcare/dragon-copilot/sdk/ambient/security-guidance#encryption-at-rest&quot;&gt;used by Microsoft in an official SDK&lt;/a&gt;. &lt;a href=&quot;#fnref-3&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;3&quot; aria-label=&quot;Back to reference 3&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-4&quot;&gt;
&lt;p&gt;Again, full disclosure, this is my design, but it’s adopted externally enough that I feel comfortable recommending it. &lt;a href=&quot;#fnref-4&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;4&quot; aria-label=&quot;Back to reference 4&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-5&quot;&gt;
&lt;p&gt;On 64-bit machines, SHA-512/256 and SHA-512 should be faster than SHA-256 thanks to their larger block size, except right now hardware support for SHA-256 is more widespread, so SHA-256 is faster in practice. They are both fast, so you probably don’t care. &lt;a href=&quot;#fnref-5&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;5&quot; aria-label=&quot;Back to reference 5&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-6&quot;&gt;
&lt;p&gt;Partially because of choices NIST made to make SHA3-256 strictly as secure as SHA-256, even where SHA-256 was overkill. SHA-256 and SHA3-256 have 128 bits of collision resistance and 256 bits of second preimage resistance. It’d be enough to have 128 bits of both, which is what you get from 256 bits of SHAKE128 output. Unfortunately, it doesn’t have a SHA name. TurboSHAKE128 (RFC 9861) would be even better, but it is neither FIPS 140 nor widespread. &lt;a href=&quot;#fnref-6&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;6&quot; aria-label=&quot;Back to reference 6&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-7&quot;&gt;
&lt;p&gt;People outside the FIPS 140 circle of hell don’t seem to have noticed, but new NIST rules require FIPS 140 modules to have an “internal” entropy source now. If your FIPS 140 “module” is a piece of software… what do you do? Everyone’s answer is “CPU jitter”: run a tight loop that accesses uncached memory and take high-resolution timestamps, hoping the frequency mismatch between CPU and main memory will make the measurements unpredictable. IMHO, this is the worst part of FIPS 140 since the module integrity hash, and it will hurt people. The one sane way to do it is to go through the motions, and then at the last moment mix the result with kernel entropy, like Go does. However, that’s &lt;em&gt;harder&lt;/em&gt; to justify to the validation labs than doing just CPU jitter, so I’m afraid we’ll see pure CPU jitter in production a lot. &lt;a href=&quot;#fnref-7&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;7&quot; aria-label=&quot;Back to reference 7&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-8&quot;&gt;
&lt;p&gt;Argon2id parameters mostly do what you’d imagine, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/the-scrypt-parameters/&quot;&gt;scrypt parameters do not&lt;/a&gt;. &lt;a href=&quot;#fnref-8&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;8&quot; aria-label=&quot;Back to reference 8&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-9&quot;&gt;
&lt;p&gt;A link with an opaque random token that logs in the opening browser is very hard to phish. A link that logs in the browser that &lt;em&gt;started&lt;/em&gt; the login is very easy to phish. Short numeric codes are moderately easy to phish. (You can get the best of both worlds by using the Broadcast Channel API to pass the token from the new tab to the original one.) &lt;a href=&quot;#fnref-9&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;9&quot; aria-label=&quot;Back to reference 9&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-10&quot;&gt;
&lt;p&gt;In a different venue I said a serious engineering community would have a post-mortem about having wasted two of the five years we had for the transition to post-quantum cryptography, only to produce specs that changed &lt;em&gt;nothing&lt;/em&gt; compared to what we had in 2024. But that’s for another time. &lt;a href=&quot;#fnref-10&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;10&quot; aria-label=&quot;Back to reference 10&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-11&quot;&gt;
&lt;p&gt;Again again, full disclosure: my design, but pretty uncontroversial pick. &lt;a href=&quot;#fnref-11&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;11&quot; aria-label=&quot;Back to reference 11&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-12&quot;&gt;
&lt;p&gt;I truly don’t understand why my predecessors recommended NaCl’s box. It’s an &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://words.filippo.io/nacl-api/&quot;&gt;incredibly sharp and low-level API&lt;/a&gt;, pretending to be a high-level safe API. &lt;a href=&quot;#fnref-12&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;12&quot; aria-label=&quot;Back to reference 12&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-13&quot;&gt;
&lt;p&gt;One last time, full disclosure: I am a crypto/tls maintainer. &lt;a href=&quot;#fnref-13&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;13&quot; aria-label=&quot;Back to reference 13&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-14&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2020-13777&quot;&gt;CVE-2020-13777&lt;/a&gt;, GnuTLS 3.6.4–3.6.13: the session ticket key is all zeroes, breaking confidentiality in TLS 1.2 and allowing authentication bypass in TLS 1.3. &lt;a href=&quot;#fnref-14&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;14&quot; aria-label=&quot;Back to reference 14&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-15&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2023-45199&quot;&gt;CVE-2023-45199&lt;/a&gt;, Mbed TLS 3.2–3.4: pre-auth remote code execution via an oversized key share in the TLS 1.3 handshake. &lt;a href=&quot;#fnref-15&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;15&quot; aria-label=&quot;Back to reference 15&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-16&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-34873&quot;&gt;CVE-2026-34873&lt;/a&gt;, Mbed TLS 3.5.0–4.0.0: TLS 1.3 session resumption allows client impersonation. &lt;a href=&quot;#fnref-16&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;16&quot; aria-label=&quot;Back to reference 16&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-17&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-25835&quot;&gt;CVE-2026-25835&lt;/a&gt;, Mbed TLS 2.18.0–3.6.5 and 4.0.0: the RNG state is cloned across fork, so parent and child produce the same random values. &lt;a href=&quot;#fnref-17&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;17&quot; aria-label=&quot;Back to reference 17&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-18&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-5194&quot;&gt;CVE-2026-5194&lt;/a&gt;, wolfSSL 3.12.0–5.9.0: signatures by ECDSA certificates can be forged by providing an ML-DSA or Ed25519 signature algorithm identifier and a trivial ECDSA signature over the zero-length digest. &lt;a href=&quot;#fnref-18&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;18&quot; aria-label=&quot;Back to reference 18&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-19&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-5501&quot;&gt;CVE-2026-5501&lt;/a&gt;, wolfSSL through 5.9.0: the OpenSSL compatibility layer skips the leaf’s signature check when any valid end-entity cert is supplied as an “intermediate.” &lt;a href=&quot;#fnref-19&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;19&quot; aria-label=&quot;Back to reference 19&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-20&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-11310&quot;&gt;CVE-2026-11310&lt;/a&gt;, wolfSSL 5.8.4–5.9.1: the OpenSSL compatibility layer lets caller-supplied untrusted intermediates act as trust anchors. &lt;a href=&quot;#fnref-20&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;20&quot; aria-label=&quot;Back to reference 20&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-21&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-11999&quot;&gt;CVE-2026-11999&lt;/a&gt;, wolfSSL 5.7.4–5.9.1: the OpenSSL compatibility layer accepts any chain longer than the maximum path depth of 100, without reaching a trust anchor. &lt;a href=&quot;#fnref-21&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;21&quot; aria-label=&quot;Back to reference 21&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-22&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-55960&quot;&gt;CVE-2026-55960&lt;/a&gt;, wolfSSL 5.6.4–5.9.1: a raw public key is always accepted in place of a certificate, bypassing authentication. &lt;a href=&quot;#fnref-22&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;22&quot; aria-label=&quot;Back to reference 22&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-23&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-93302&quot;&gt;CVE-2026-93302&lt;/a&gt;, wolfSSL 5.3.0–5.9.2: trusted-peer certificates are matched by name rather than public key. &lt;a href=&quot;#fnref-23&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;23&quot; aria-label=&quot;Back to reference 23&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-24&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-89136&quot;&gt;CVE-2026-89136&lt;/a&gt;, wolfSSL 5.6.0–5.9.2: clients still accept an unsolicited raw public key from the server, bypassing authentication. &lt;a href=&quot;#fnref-24&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;24&quot; aria-label=&quot;Back to reference 24&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-25&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-89135&quot;&gt;CVE-2026-89135&lt;/a&gt;, wolfSSL 5.8.4–5.9.2: a failed chain verification in the OpenSSL compatibility layer permanently adds the attacker’s CA to the trust store used by native TLS, OCSP, and CRL checks. &lt;a href=&quot;#fnref-25&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;25&quot; aria-label=&quot;Back to reference 25&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-26&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-34580&quot;&gt;CVE-2026-34580&lt;/a&gt;, Botan 3.11.0: an end-entity certificate whose DN matches a trusted root is accepted as if it were that root. &lt;a href=&quot;#fnref-26&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;26&quot; aria-label=&quot;Back to reference 26&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn-27&quot;&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://nvd.nist.gov/vuln/detail/CVE-2026-34582&quot;&gt;CVE-2026-34582&lt;/a&gt;, Botan before 3.11.1: TLS 1.3 servers process application data before Finished, so a client can skip Certificate, CertificateVerify, and Finished entirely, a full client authentication bypass. &lt;a href=&quot;#fnref-27&quot; class=&quot;footnote-backref&quot; data-footnote-backref data-footnote-backref-idx=&quot;27&quot; aria-label=&quot;Back to reference 27&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
</feed>
