<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: tls</title><description>Posts tagged tls.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 07 Jun 2026 04:13:15 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/tls/rss.xml" rel="self" type="application/rss+xml"/><item><title>Rotating Every Cipher: SChannel and the Twenty-Year Algorithm-Agility Story of Windows TLS</title><link>https://paragmali.com/blog/rotating-every-cipher-schannel-and-the-twenty-year-algorithm/</link><guid isPermaLink="true">https://paragmali.com/blog/rotating-every-cipher-schannel-and-the-twenty-year-algorithm/</guid><description>How one Windows DLL rotated every TLS primitive from RC4 to ML-KEM without breaking IIS, RDP, SQL Server, or .NET SslStream -- and why Vista&apos;s 2007 CNG was the inflection point.</description><pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate><content:encoded>
Windows speaks TLS through **SChannel**, the SSPI provider in `schannel.dll` [@ms-learn-schannel-ssp]. Across roughly twenty years SChannel has rotated every cryptographic primitive in its default cipher list -- from RSA key transport and RC4 to ECDHE, AES-GCM, and ML-KEM -- without breaking IIS, RDP, SQL Server, LDAPS, WinHTTP, or .NET `SslStream`. That was only possible because Microsoft, in Vista&apos;s 2007 **CNG** (Cryptography API: Next Generation), made algorithm agility a first-class architectural property [@ms-learn-cng-portal]: BCrypt for primitive dispatch, NCrypt for key custodians, SymCrypt as the unified FIPS-validated backend [@symcrypt-github]. This article walks the substrate from CryptoAPI 1.0 through CNG and SymCrypt, the five cipher-suite generations the substrate carried, the 2014 MS14-066 / WinShock RCE (which was *not* Heartbleed) [@ms14-066], the certificate-validation pipeline, and the in-flight post-quantum hybrid TLS 1.3 rollout (`X25519MLKEM768`, FIPS 203) [@fips-203][@ms-learn-cng-mlkem-examples].
&lt;h2&gt;1. Two PowerShell Outputs, Twelve Years Apart&lt;/h2&gt;
&lt;p&gt;Run &lt;code&gt;Get-TlsCipherSuite&lt;/code&gt; on a freshly installed Windows Server 2025 and the output is unrecognisable to a 2012 administrator [@ms-learn-get-tlsciphersuite]. RC4 is gone. 3DES is gone. The list is led by &lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt; and &lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt; -- TLS 1.3 cipher suites that did not exist when &lt;code&gt;schannel.dll&lt;/code&gt; was first written. Yet IIS, SQL Server, RDP via CredSSP, LDAPS, WinHTTP, and every .NET &lt;code&gt;SslStream&lt;/code&gt; consumer on the planet still compiles against the same Win32 SSPI surface they did in 2007 [@ms-learn-schannel-ssp]. How does one DLL rotate every cryptographic primitive in its lineup without breaking the world above it?&lt;/p&gt;
&lt;p&gt;That question is this article&apos;s organising prompt. The answer, held back deliberately until Section 4, is &lt;strong&gt;algorithm agility&lt;/strong&gt; -- the architectural property Microsoft made first-class when it shipped Cryptography API: Next Generation alongside Windows Vista in early 2007 [@ms-learn-cng-portal].&lt;/p&gt;

The Win32 abstraction that lets an application acquire credentials, build a security context, and exchange authentication tokens without knowing which protocol (Kerberos, NTLM, Negotiate, or **Schannel**) is doing the work underneath. SChannel is the SSP that implements SSL, TLS, and DTLS on Windows; its module is `schannel.dll` and its public surface is `AcquireCredentialsHandle` / `InitializeSecurityContext` / `AcceptSecurityContext` [@ms-learn-schannel-ssp].
&lt;h3&gt;Which Windows endpoints SChannel actually owns&lt;/h3&gt;
&lt;p&gt;SChannel is not the only TLS stack that runs on Windows, but the Windows TLS endpoints Microsoft itself owns all run through it. SChannel is the SSP behind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IIS&lt;/strong&gt; TLS termination for HTTP/1.1 and HTTP/2 (HTTP/3 over QUIC terminates in &lt;code&gt;msquic.dll&lt;/code&gt;, which uses SChannel for the TLS 1.3 handshake key derivation and then performs the per-packet AEAD outside &lt;code&gt;schannel.dll&lt;/code&gt; per RFC 9001 §5 [@msquic-tls-md][@rfc-9001]).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RDP&lt;/strong&gt; Network Level Authentication via CredSSP -- the CredSSP SSP wraps SChannel to deliver the TLS-protected credential prompt before the RDP session opens.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LDAPS&lt;/strong&gt; for Active Directory client and server bindings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RPC over HTTPS&lt;/strong&gt; as used by Outlook Anywhere and historical Exchange topologies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL Server&lt;/strong&gt; TDS-over-TLS encryption on Windows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WinHTTP&lt;/strong&gt; and &lt;strong&gt;WinINet&lt;/strong&gt; -- the Win32 HTTP clients behind &lt;code&gt;BITS&lt;/code&gt;, &lt;code&gt;WebClient&lt;/code&gt;, and many enterprise agents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.NET &lt;code&gt;SslStream&lt;/code&gt;&lt;/strong&gt; when running on Windows. On Linux .NET delegates to OpenSSL; on macOS it uses Apple&apos;s Network framework.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The endpoints SChannel does &lt;em&gt;not&lt;/em&gt; own on a typical Windows box are equally important to name. &lt;strong&gt;Chromium and (via Chromium) Microsoft Edge&lt;/strong&gt; ship BoringSSL -- legacy EdgeHTML used Windows native crypto, but it has been end-of-life since Edge&apos;s January 15, 2020 Chromium-based re-launch. &lt;strong&gt;Firefox&lt;/strong&gt; ships NSS. &lt;strong&gt;Containerised .NET workloads on Linux&lt;/strong&gt; ship with OpenSSL. &lt;strong&gt;SQL Server on Linux&lt;/strong&gt; uses OpenSSL too [@boringssl-readme][@dotnet-cross-platform-crypto]. The Windows TLS story is genuinely a Windows-platform story, not a &quot;what speaks TLS on a Windows machine&quot; story.On Linux, .NET&apos;s &lt;code&gt;SslStream&lt;/code&gt; does not use SChannel at all -- it delegates to OpenSSL [@dotnet-cross-platform-crypto]. The Win32 SChannel story really is a Windows-platform story, not a story about everything TLS-shaped that happens on a Windows machine.MsQuic uses SChannel only for the TLS 1.3 handshake key derivation -- the per-packet AEAD that protects QUIC payloads runs &lt;em&gt;outside&lt;/em&gt; &lt;code&gt;schannel.dll&lt;/code&gt;, in MsQuic itself, per RFC 9001 §5 packet protection [@msquic-tls-md][@rfc-9001]. The MsQuic project documents the TLS abstraction layer (&lt;code&gt;CxPlatTlsProcessData&lt;/code&gt;) and notes explicitly that &quot;the TLS record layer is not included&quot; and that &quot;TLS exposes the encryption key material to QUIC to secure its own packets&quot; [@msquic-tls-md].&lt;/p&gt;
&lt;h3&gt;The artifact comparison&lt;/h3&gt;
&lt;p&gt;The cleanest way to see the substrate&apos;s twenty-year track record is to compare what &lt;code&gt;Get-TlsCipherSuite&lt;/code&gt; returns on two Windows generations [@ms-learn-get-tlsciphersuite][@ms-learn-cipher-suites-schannel]. The TLS 1.3 cipher suites listed on the Windows Server 2022 / 2025 page (&lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt;, &lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt;, &lt;code&gt;TLS_CHACHA20_POLY1305_SHA256&lt;/code&gt;) [@ms-learn-tls-cipher-suites-server-2022] simply are not on the Windows 7 / Server 2008 R2 page [@ms-learn-tls-cipher-suites-windows-7]; conversely, the Windows 7 page enumerates &lt;code&gt;TLS_RSA_WITH_RC4_128_SHA&lt;/code&gt;, &lt;code&gt;TLS_RSA_WITH_3DES_EDE_CBC_SHA&lt;/code&gt;, and &lt;code&gt;TLS_RSA_WITH_AES_128_CBC_SHA&lt;/code&gt; as enabled by default -- suites that newer Windows builds have either removed or moved off-by-default [@ms-learn-tls-registry-settings].&lt;/p&gt;
&lt;p&gt;{`
// Approximation of the SChannel cipher-suite roster on two Windows generations.
const server2012R2 = [
  &apos;TLS_RSA_WITH_RC4_128_SHA&apos;,
  &apos;TLS_RSA_WITH_3DES_EDE_CBC_SHA&apos;,
  &apos;TLS_RSA_WITH_AES_128_CBC_SHA&apos;,
  &apos;TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA&apos;,
  &apos;TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256&apos;,
];&lt;/p&gt;
&lt;p&gt;const server2025 = [
  &apos;TLS_AES_256_GCM_SHA384&apos;,
  &apos;TLS_AES_128_GCM_SHA256&apos;,
  &apos;TLS_CHACHA20_POLY1305_SHA256&apos;,
  &apos;TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384&apos;,
  &apos;TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256&apos;,
];&lt;/p&gt;
&lt;p&gt;const rotatedOut = server2012R2.filter(s =&amp;gt; !server2025.includes(s));
const rotatedIn  = server2025.filter(s =&amp;gt; !server2012R2.includes(s));&lt;/p&gt;
&lt;p&gt;console.log(&apos;Rotated out (2012R2 default -&amp;gt; 2025 absent):&apos;);
rotatedOut.forEach(s =&amp;gt; console.log(&apos;  - &apos; + s));
console.log(&apos;Rotated in (2025 default -&amp;gt; 2012R2 unavailable):&apos;);
rotatedIn.forEach(s =&amp;gt; console.log(&apos;  + &apos; + s));
`}&lt;/p&gt;
&lt;p&gt;The whole journey, on one timeline:&lt;/p&gt;

gantt
    dateFormat YYYY
    axisFormat %Y
    title SChannel substrate eras and primitive rotations
    section Substrate
    CryptoAPI 1.0 (CSPs)        :crit, capi, 1996, 2007
    CNG (BCrypt + NCrypt)       :active, cng, 2007, 2026
    SymCrypt unified engine     :sym, 2017, 2026
    section Protocol versions
    SSL 2.0 / 3.0 / TLS 1.0     :p10, 1996, 2014
    TLS 1.1 / 1.2               :p12, 2008, 2026
    TLS 1.3 default-on          :p13, 2021, 2026
    section Cipher generations
    ECDHE plus AES-GCM debut    :g1, 2009, 2026
    RC4 deprecation             :g2, 2013, 2016
    3DES retirement             :g3, 2019, 2026
    SHA-1 sunset                :g4, 2016, 2022
    TLS 1.0 / 1.1 off-default   :g5, 2020, 2025
    X25519MLKEM768 hybrid       :g6, 2025, 2026
&lt;p&gt;CNG did not exist for the first eleven years of SChannel&apos;s life. To see why CNG had to be invented, the next section walks the rigidity that almost broke the Windows TLS stack before AES could even be standardised.&lt;/p&gt;
&lt;h2&gt;2. Before CNG: PCT 1.0, SSL, and the Tyranny of &lt;code&gt;ALG_ID&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;In October 1995 a five-author byline from Microsoft Corporation -- Josh Benaloh, Butler Lampson, Daniel Simon, Terence Spies, and Bennet Yee -- posted &lt;code&gt;draft-benaloh-pct-00&lt;/code&gt; to the IETF [@draft-benaloh-pct-00]. The draft introduced &lt;strong&gt;Private Communication Technology version 1&lt;/strong&gt;, a protocol whose abstract reads: &quot;this protocol corrects or improves on several weaknesses of SSL.&quot; The byline matters. Butler Lampson had received the Turing Award in 1992. Microsoft was not toying with PCT; it intended to win the secure-transport standardisation race.Butler Lampson&apos;s appearance on the PCT 1.0 draft byline (alongside Benaloh, Simon, Spies, Yee) is not incidental. Lampson won the Turing Award in 1992. Microsoft was serious about PCT 1.0 as a protocol, not merely an implementation.&lt;/p&gt;
&lt;p&gt;PCT lost. By the time &lt;code&gt;schannel.dll&lt;/code&gt; shipped in Windows NT 4.0 (commonly placed in 1996 per contemporary release histories), the new SChannel SSP had to negotiate three incompatible handshakes on the same wire: SSL 2.0, SSL 3.0, and PCT 1.0 [@ms-learn-schannel-ssp]. By Vista, PCT was gone; SSL 2.0 was on its way to formal IETF prohibition [@rfc-6176]; SSL 3.0 had a few years left before POODLE would kill it off in 2014 [@poodle-pdf]. The protocol-level story is well-trodden. The substrate underneath -- the &lt;em&gt;engine&lt;/em&gt; SChannel called into to compute each primitive -- is what made the next decade much harder than it had to be.&lt;/p&gt;
&lt;h3&gt;CryptoAPI 1.0 and the CSP cage&lt;/h3&gt;

A loadable DLL that implements a fixed catalog of cryptographic operations under **CryptoAPI 1.0**. Each CSP advertises a *provider type* (e.g. `PROV_RSA_FULL`, `PROV_RSA_SCHANNEL`) and exposes its primitives through opaque `ALG_ID` constants such as `CALG_RC4`, `CALG_3DES`, and `CALG_SHA1`. Adding a new primitive meant shipping a new CSP DLL, registering it under `HKLM\Software\Microsoft\Cryptography\Defaults\Provider`, and threading a fresh BLOB type through every consumer that called `CryptAcquireContext`.
&lt;p&gt;The CryptoAPI 1.0 model had a single fatal property: the primitive &lt;em&gt;was&lt;/em&gt; the API. To compute SHA-256, code had to ask CAPI for an &lt;code&gt;ALG_ID&lt;/code&gt; whose numeric value was &lt;code&gt;CALG_SHA_256&lt;/code&gt; -- and that constant only existed once Microsoft shipped a CSP that defined it, in the same OS release that introduced the algorithm [@ms-learn-alg-id]. Elliptic-curve cryptography never arrived in CAPI in any usable form; the &lt;code&gt;ALG_ID + key BLOB&lt;/code&gt; shape simply could not express the named curves, parameter sets, point-compression flags, or per-curve coordinate sizes that ECC required.&lt;/p&gt;
&lt;p&gt;So in the early 2000s SChannel&apos;s cipher-suite list was less a menu of cryptography and more a snapshot of what CSPs had shipped. FIPS 197 (the AES standard) was published in November 2001. Windows XP shipped without AES in its default SChannel cipher list and only got it broadly via Service Pack 3 and Server 2003. &lt;strong&gt;The four-year AES gap was not Microsoft dragging its feet -- it was the thickness of a CSP-rev cycle.&lt;/strong&gt; RC4 dominance, 3DES persistence, 1024-bit RSA inertia, no ECC: these were the substrate&apos;s fingerprints, not the vendor&apos;s preferences.&lt;/p&gt;

flowchart LR
    A[Application -- IIS / IE / RPC] --&amp;gt; B[SChannel SSP]
    B --&amp;gt; C[CryptoAPI 1.0 / CryptAcquireContext]
    C --&amp;gt; D[&quot;RSA SChannel CSP -- ALG_ID lookup&quot;]
    C --&amp;gt; E[&quot;Base / Enhanced CSP -- ALG_ID lookup&quot;]
    C --&amp;gt; F[Smart Card CSP]
    D -. &quot;Adding ECC requires a new CSP, new ALG_ID, new BLOB type, new IANA codepoint&quot; .-&amp;gt; G((Friction))
    E -. &quot;Adding SHA-256 requires CSP rev + OS release&quot; .-&amp;gt; G
&lt;h3&gt;The PCT failure as a &lt;em&gt;positive&lt;/em&gt; lesson&lt;/h3&gt;
&lt;p&gt;PCT&apos;s loss is, in retrospect, the strongest early case for algorithm agility. SChannel had shipped PCT-the-protocol in 1996; by 2007 PCT was a footnote and SChannel was speaking TLS 1.0, TLS 1.1, SSL 3.0, and (with the right service pack) early TLS 1.2 drafts. The application surface above SChannel did not flinch. Microsoft had bet on PCT, lost, rotated to TLS, and shipped the rotation through the &lt;em&gt;protocol&lt;/em&gt; abstraction that the SSP boundary provided.&lt;/p&gt;
&lt;p&gt;What the SSP boundary did &lt;em&gt;not&lt;/em&gt; shield was the primitive layer. Algorithm rotation had to happen one CSP rev at a time. By the mid-2000s Microsoft&apos;s engineering leadership had a clear diagnosis: the protocol abstraction worked; the primitive abstraction did not. The next CSP rev would not save them, because there were not enough CSP revs in the future to keep up with what cryptography was about to do -- ECC was already standardised, AEAD constructions were being designed, and the post-quantum research had been live for a decade.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The early-2000s lag in AES adoption, the persistence of RC4 and 3DES, and the absence of ECC in Windows were not vendor laziness. CryptoAPI 1.0&apos;s &lt;code&gt;ALG_ID&lt;/code&gt; + provider-type model was &lt;em&gt;structurally incapable&lt;/em&gt; of representing ECC&apos;s named curves and parameter sets. The right question was never &quot;why is Microsoft slow?&quot; -- it was &quot;what would a Windows cryptographic substrate that was &lt;em&gt;not&lt;/em&gt; slow look like?&quot; The Vista CNG redesign is what that question&apos;s answer looks like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;RC4 dominance, 3DES persistence, 1024-bit RSA inertia, no ECC -- these were not laziness, they were the substrate. A fix to TLS 1.0 was easy; a fix to &lt;em&gt;the way Windows let an application reach a primitive&lt;/em&gt; was a rewrite.&lt;/p&gt;
&lt;h2&gt;3. Configuration Agility Without Substrate Agility: XP and Server 2003&lt;/h2&gt;
&lt;p&gt;Consider a single registry path: &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client\DisabledByDefault&lt;/code&gt;. That value was introduced for SChannel&apos;s TLS 1.0 support in the XP / Server 2003 era. It is &lt;em&gt;exactly&lt;/em&gt; the same registry sub-tree path an operator uses in 2026 to disable TLS 1.0 itself [@ms-learn-tls-registry-settings]. The configuration surface from 1999 has outlasted three generations of TLS.&lt;/p&gt;
&lt;p&gt;This is not an accident. By the time TLS 1.0 landed in Windows (RFC 2246, Tim Dierks and Christopher Allen at Certicom, January 1999 [@rfc-2246]) and TLS 1.1 followed (RFC 4346, Dierks and Eric Rescorla, April 2006 [@rfc-4346]), SChannel had developed an emerging design pattern: every protocol version became a sub-key, every cipher suite became a registry-driven enable/disable, and the &lt;code&gt;SSL Cipher Suite Order&lt;/code&gt; Group Policy gave administrators a single rope to pull when an algorithm fell from grace.&lt;/p&gt;
&lt;p&gt;That model has aged well. Microsoft&apos;s current &lt;code&gt;tls-registry-settings&lt;/code&gt; page is essentially the same structural document it would have been twenty years ago, with new sub-keys for each new protocol version (SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1, TLS 1.2, TLS 1.3, DTLS 1.0, DTLS 1.2) and new values for the policy levers Microsoft has added along the way [@ms-learn-tls-registry-settings].The same &lt;code&gt;SCHANNEL\Protocols\&amp;lt;ver&amp;gt;\&amp;lt;role&amp;gt;\Enabled&lt;/code&gt; pattern handles SSL 2, SSL 3, TLS 1.0, TLS 1.1, TLS 1.2, TLS 1.3, DTLS 1.0, and DTLS 1.2. A single sub-key per protocol; new versions slot in without reorganising the hive.&lt;/p&gt;
&lt;h3&gt;The four sub-keys an XP / Server 2003 box exposed&lt;/h3&gt;
&lt;p&gt;The shape of the &lt;code&gt;SCHANNEL\&lt;/code&gt; hive on a representative Server 2003 R2 box, reconstructed from Microsoft Knowledge Base article KB245030 (&quot;How to restrict the use of certain cryptographic algorithms and protocols in Schannel.dll&quot;) and the modern Microsoft Learn &lt;code&gt;tls-registry-settings&lt;/code&gt; page that preserves the same structural document [@ms-learn-tls-registry-settings], is shown below.Microsoft Knowledge Base article KB245030 (&quot;How to restrict the use of certain cryptographic algorithms and protocols in Schannel.dll&quot;) is the *origin document* for the four-sub-key SCHANNEL\ registry pattern this section dumps. The original &lt;code&gt;support.microsoft.com&lt;/code&gt; URL now returns HTTP 404; the same content lives at Microsoft Learn&apos;s &lt;code&gt;tls-registry-settings&lt;/code&gt; page [@ms-learn-tls-registry-settings]. The four sub-keys (&lt;code&gt;Protocols&lt;/code&gt;, &lt;code&gt;Ciphers&lt;/code&gt;, &lt;code&gt;Hashes&lt;/code&gt;, &lt;code&gt;KeyExchangeAlgorithms&lt;/code&gt;) have been stable since Windows 2000. The DWORD convention is itself the agility affordance: &lt;code&gt;0xffffffff&lt;/code&gt; means &quot;enabled,&quot; &lt;code&gt;0&lt;/code&gt; means &quot;disabled,&quot; and the &lt;code&gt;Server&lt;/code&gt; versus &lt;code&gt;Client&lt;/code&gt; role split lets an admin disable SSL 2.0 &lt;em&gt;server&lt;/em&gt;-side without breaking outbound HTTPS &lt;em&gt;client&lt;/em&gt;-side during the transition.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\
  Protocols\
    SSL 2.0\Server\Enabled = 0xffffffff   ; ON by default on 2003 R2
    SSL 3.0\Server\Enabled = 0xffffffff
    TLS 1.0\Server\Enabled = 0xffffffff
    ; TLS 1.1 / 1.2 sub-keys absent -- those protocols do not exist on 2003 R2
  Ciphers\
    RC4 128/128\Enabled        = 0xffffffff
    RC4 56/128\Enabled         = 0xffffffff   ; export-grade, still present
    RC4 40/128\Enabled         = 0xffffffff   ; export-grade
    Triple DES 168\Enabled     = 0xffffffff
    DES 56/56\Enabled          = 0xffffffff
    RC2 40/128\Enabled         = 0xffffffff   ; export-grade
    NULL\Enabled               = 0
  Hashes\
    MD5\Enabled                = 0xffffffff
    SHA\Enabled                = 0xffffffff
  KeyExchangeAlgorithms\
    Diffie-Hellman\Enabled     = 0xffffffff
    PKCS\Enabled               = 0xffffffff   ; RSA key transport
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice what is &lt;em&gt;not&lt;/em&gt; there. No &lt;code&gt;TLS 1.1&lt;/code&gt;, no &lt;code&gt;TLS 1.2&lt;/code&gt;, no AES sub-key (AES &lt;code&gt;ALG_ID&lt;/code&gt; constants arrived in &lt;code&gt;rsaenh.dll&lt;/code&gt; via XP SP3 and Server 2003 SP2 but SChannel had to learn the suite-name strings separately). No ECC primitive &lt;em&gt;at all&lt;/em&gt; -- CryptoAPI 1.0 could not express named curve parameters in the &lt;code&gt;ALG_ID + key BLOB&lt;/code&gt; shape, so no amount of registry editing could unlock an ECDHE cipher suite on a 2003-era box. The four-sub-key layout (&lt;code&gt;Protocols&lt;/code&gt;, &lt;code&gt;Ciphers&lt;/code&gt;, &lt;code&gt;Hashes&lt;/code&gt;, &lt;code&gt;KeyExchangeAlgorithms&lt;/code&gt;) is the &lt;em&gt;configuration surface&lt;/em&gt;; what the surface can offer is bounded by the &lt;em&gt;substrate&lt;/em&gt; underneath it.&lt;/p&gt;
&lt;h3&gt;The CSP layer underneath: &lt;code&gt;PROV_RSA_SCHANNEL&lt;/code&gt; and &lt;code&gt;CALG_TLS1PRF&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;On the dispatch side of that same XP / 2003 box, SChannel relied on two CryptoAPI 1.0 Cryptographic Service Providers in particular. The Microsoft Learn &quot;Cryptographic Provider Types&quot; page enumerates the provider types Microsoft shipped [@ms-learn-cryptographic-provider-types]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PROV_RSA_SCHANNEL&lt;/code&gt;&lt;/strong&gt; (provider type &lt;code&gt;12&lt;/code&gt;) -- the SChannel-private CSP. It carried the TLS-specific primitives: the &lt;code&gt;CALG_TLS1PRF&lt;/code&gt; pseudorandom function (algorithm identifier &lt;code&gt;0x0000800a&lt;/code&gt;), the &lt;code&gt;CALG_SCHANNEL_MASTER_HASH&lt;/code&gt; and &lt;code&gt;CALG_SCHANNEL_MAC_KEY&lt;/code&gt; and &lt;code&gt;CALG_SCHANNEL_ENC_KEY&lt;/code&gt; key-derivation handles, and (because the substrate had to negotiate three handshake protocols) the &lt;code&gt;CALG_SSL2_MASTER&lt;/code&gt; and &lt;code&gt;CALG_PCT1_MASTER&lt;/code&gt; constants documented on the Microsoft Learn &lt;code&gt;ALG_ID&lt;/code&gt; page [@ms-learn-alg-id].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;PROV_RSA_FULL&lt;/code&gt; / &lt;code&gt;PROV_RSA_AES&lt;/code&gt; (&lt;code&gt;rsaenh.dll&lt;/code&gt;)&lt;/strong&gt; -- the general-purpose enhanced CSP, which carried the bulk symmetric primitives the cipher list named (&lt;code&gt;CALG_RC4&lt;/code&gt;, &lt;code&gt;CALG_DES&lt;/code&gt;, &lt;code&gt;CALG_3DES&lt;/code&gt;, eventually &lt;code&gt;CALG_AES_128&lt;/code&gt;, &lt;code&gt;CALG_AES_256&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both CSPs were loaded by &lt;code&gt;CryptAcquireContext&lt;/code&gt; against the &lt;code&gt;HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider&lt;/code&gt; registry hierarchy. Neither was extensible without an &lt;code&gt;rsaenh.dll&lt;/code&gt; (or analogous) revision and a CSP-rev ship cycle. The registry hive let an operator turn primitives off; it could not let an operator turn a &lt;em&gt;new&lt;/em&gt; primitive on, because the CSP catalog itself was the menu. Adding ECC to that menu was not a configuration problem -- it required a different substrate.&lt;/p&gt;
&lt;h3&gt;The &lt;code&gt;SSL Cipher Suite Order&lt;/code&gt; GPO -- and why it is a Vista-era artifact, not a 2003-era one&lt;/h3&gt;
&lt;p&gt;Cipher-suite &lt;em&gt;ordering&lt;/em&gt; (as opposed to &lt;em&gt;enablement&lt;/em&gt;) was not exposed as an administrative tunable until Windows Vista and Server 2008 added the &lt;code&gt;Computer Configuration &amp;gt; Administrative Templates &amp;gt; Network &amp;gt; SSL Configuration Settings &amp;gt; SSL Cipher Suite Order&lt;/code&gt; Group Policy. The current Microsoft Learn &quot;Manage Transport Layer Security (TLS)&quot; page documents the format verbatim: &quot;a strict comma delimited format. Each cipher suite string ends with a comma to the right side of it... the list of cipher suites is limited to 1,023 characters.&quot; [@ms-learn-manage-tls] A representative XP-era ordering string -- if the GPO had existed for the operator to set -- would have read something like &lt;code&gt;TLS_RSA_WITH_RC4_128_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_DES_CBC_SHA,...&lt;/code&gt;, walking the actual Server 2003 default lineup that the CSP catalog could deliver. The fact that this lever did not exist on 2003 -- the operator was limited to flipping &lt;code&gt;Ciphers\&amp;lt;name&amp;gt;\Enabled&lt;/code&gt; DWORDs in the per-cipher sub-tree -- is itself evidence of how the &lt;em&gt;operator-facing&lt;/em&gt; SChannel surface matured one Windows release at a time.&lt;/p&gt;
&lt;h3&gt;No enumeration tool on Server 2003&lt;/h3&gt;
&lt;p&gt;There is no &lt;code&gt;Get-TlsCipherSuite&lt;/code&gt; cmdlet on Windows Server 2003. Windows PowerShell itself only shipped (as KB968930) in 2009, and the &lt;code&gt;TLS&lt;/code&gt; PowerShell module first appeared in Windows 8 and Server 2012 [@ms-learn-get-tlsciphersuite]. On a 2003-era box the empirical answer to &quot;what does this server actually negotiate?&quot; was either a &lt;code&gt;reg query &quot;HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server&quot; /v Enabled&lt;/code&gt; against the registry sub-tree above, or -- for what the &lt;em&gt;client&lt;/em&gt; actually picked -- an outbound Internet Explorer 6 trace, or -- for what the &lt;em&gt;server&lt;/em&gt; actually accepted -- a TCP-connect dump against port 443 with a TLS scanner of the era (typically &lt;code&gt;openssl s_client -connect host:443 -cipher ALL&lt;/code&gt; running on a separately-administered Linux box). The operator-visible inventory tool an admin reaches for in 2026 is itself a CNG-era artifact.&lt;/p&gt;
&lt;h3&gt;The agility split: configuration vs. substrate&lt;/h3&gt;
&lt;p&gt;Here is the structural problem that XP-era SChannel revealed. The configuration surface was getting more agile -- an operator could turn cipher suites on and off, prefer one over another, disable an entire protocol version -- but the &lt;em&gt;engine&lt;/em&gt; underneath was not. New primitives still required a CSP rev. New named curves were unrepresentable. SHA-256 in TLS handshake signatures was a several-year project.&lt;/p&gt;
&lt;p&gt;A useful metaphor: configuration agility without substrate agility is a treadmill. You can disable bad cipher suites at will. You cannot &lt;em&gt;add&lt;/em&gt; a new family of primitives without rebuilding the engine. By the mid-2000s Microsoft had two options. Patch CAPI in place forever -- absorb every new algorithm as a new &lt;code&gt;ALG_ID&lt;/code&gt; constant, a new CSP DLL, a new BLOB type, a new round of partner re-certification. Or ship a successor.&lt;/p&gt;

Every Microsoft technology that needed cryptography was caught in the same trap as SChannel. IPsec, EFS, BitLocker&apos;s predecessors, S/MIME in Outlook, smart-card login, Authenticode code-signing verification -- all dispatched through CryptoAPI 1.0 CSPs. The agility problem was not localised to TLS; it was the *Windows cryptography* problem. The successor Microsoft built would therefore have to be the substrate for *all* of these consumers, not just for SChannel. That is exactly what CNG ended up being.
&lt;p&gt;They chose the second. The next section is the eureka moment the rest of the article hangs on.&lt;/p&gt;
&lt;h2&gt;4. CNG: Where Vista Made Algorithm Agility First-Class (January 2007)&lt;/h2&gt;
&lt;p&gt;Vista is where the article&apos;s clock starts. In January 2007 Microsoft did not patch CryptoAPI 1.0; it shipped a parallel substrate alongside it: &lt;strong&gt;Cryptography API: Next Generation&lt;/strong&gt;. The Microsoft Learn portal still describes it in one sentence that doubles as the article&apos;s thesis: &quot;CNG is the long-term replacement for the CryptoAPI. CNG is designed to be extensible at many levels and cryptography agnostic in behavior.&quot; [@ms-learn-cng-portal]&lt;/p&gt;

CNG is the long-term replacement for the CryptoAPI. CNG is designed to be extensible at many levels and cryptography agnostic in behavior. -- Microsoft Learn, *Cryptography API: Next Generation* portal [@ms-learn-cng-portal]
&lt;p&gt;The two splits that look prosaic in the documentation -- BCrypt for primitives, NCrypt for key custodians -- are, in fact, the single architectural decision that makes the rest of this article&apos;s twenty-year story possible.&lt;/p&gt;

The post-Vista replacement for CryptoAPI 1.0. CNG splits cryptography into two API surfaces. **BCrypt** (`bcrypt.dll`) handles primitive operations (hashes, ciphers, key-agreement, signing) and addresses algorithms by string identifier through `BCryptOpenAlgorithmProvider`. **NCrypt** (`ncrypt.dll`) handles key storage and custody through pluggable Key Storage Providers (KSPs). CNG is the substrate Microsoft built so that every later cryptographic primitive could be added as a provider update rather than an API rewrite [@ms-learn-cng-portal].
&lt;h3&gt;BCrypt: algorithms become strings&lt;/h3&gt;
&lt;p&gt;The shape of the BCrypt API is the eureka moment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;BCRYPT_ALG_HANDLE hAlg;
NTSTATUS status = BCryptOpenAlgorithmProvider(
    &amp;amp;hAlg,
    BCRYPT_AES_ALGORITHM,       // string identifier
    NULL,                        // default provider
    0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;BCRYPT_AES_ALGORITHM&lt;/code&gt; is the literal string &lt;code&gt;&quot;AES&quot;&lt;/code&gt;. The handle returned by &lt;code&gt;BCryptOpenAlgorithmProvider&lt;/code&gt; does not encode which DLL implements AES; it encodes the &lt;em&gt;contract&lt;/em&gt; that the resulting handle satisfies (block cipher, configurable mode, configurable key length). The same shape later admits &lt;code&gt;BCRYPT_ECDH_P256_ALGORITHM&lt;/code&gt;, &lt;code&gt;BCRYPT_SHA384_ALGORITHM&lt;/code&gt;, &lt;code&gt;BCRYPT_CHACHA20_POLY1305_ALG_HANDLE&lt;/code&gt;, and -- in 2024-2026 -- &lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt; with parameter-set selectors such as &lt;code&gt;BCRYPT_MLKEM_PARAMETER_SET_768&lt;/code&gt; [@ms-learn-cng-mlkem-examples].&lt;/p&gt;
&lt;p&gt;&lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt; resolves through the exact same hash-table lookup as &lt;code&gt;BCRYPT_AES_ALGORITHM&lt;/code&gt; did in 2007. The substrate did not need an architectural change to absorb a brand-new algorithm family seventeen years later -- the dispatch was already built for it.&lt;/p&gt;
&lt;h3&gt;NCrypt: key custodians become pluggable&lt;/h3&gt;

**BCrypt** is the CNG API for *primitives* -- arithmetic that takes plaintext and a key and returns ciphertext (or hash, or signature). **NCrypt** is the CNG API for *key custodians* -- objects that own a private key and expose only signing, decryption, and key-derivation operations. The split lets a TLS server hold a private key whose material it never sees: SChannel calls `NCryptSignHash` against an `NCRYPT_KEY_HANDLE`, and the handle&apos;s owning KSP (software, smart card, or TPM) performs the operation in its own trust boundary.

A CNG-loadable module that owns the lifecycle and operations of a private key. Microsoft ships three out of the box: the **Microsoft Software KSP** (keys at rest in the user or machine profile, protected by DPAPI), the **Microsoft Smart Card KSP** (keys on a PIV / CCID device), and the **Microsoft Platform Crypto Provider** (keys non-exportable from the TPM 2.0). Third parties ship KSPs for HSMs and cloud KMS systems. SChannel sees only the `NCRYPT_KEY_HANDLE`; the custodian is opaque to the SSP.
&lt;h3&gt;How SChannel uses CNG&lt;/h3&gt;
&lt;p&gt;After Vista, SChannel&apos;s internals look very different. The cipher-suite registry resolves to BCrypt algorithm identifiers rather than &lt;code&gt;ALG_ID&lt;/code&gt; constants. The credentials handle that an IIS worker process receives from &lt;code&gt;AcquireCredentialsHandle&lt;/code&gt; holds an &lt;code&gt;NCRYPT_KEY_HANDLE&lt;/code&gt; for the server certificate&apos;s private key; signing operations during the handshake (CertificateVerify) dispatch through &lt;code&gt;NCryptSignHash&lt;/code&gt; to whichever KSP owns the key.&lt;/p&gt;

flowchart TD
    A[&quot;IIS / SQL Server / SslStream / WinHTTP&quot;] --&amp;gt; B[SChannel SSP -- schannel.dll]
    B --&amp;gt; C[&quot;BCrypt -- bcrypt.dll&quot;]
    B --&amp;gt; D[&quot;NCrypt -- ncrypt.dll&quot;]
    C --&amp;gt; E[&quot;SymCrypt primitive engine&quot;]
    C --&amp;gt; F[&quot;Third-party BCrypt providers&quot;]
    D --&amp;gt; G[Microsoft Software KSP]
    D --&amp;gt; H[Smart Card KSP]
    D --&amp;gt; I[&quot;Microsoft Platform Crypto Provider -- TPM 2.0&quot;]
    D --&amp;gt; J[&quot;HSM / cloud KMS KSPs&quot;]
    E --&amp;gt; K[(Algorithm dispatch by string identifier)]
    G --&amp;gt; L[(Key operations by opaque handle)]
&lt;h3&gt;The agility property, stated forward&lt;/h3&gt;
&lt;p&gt;From 2007 onward, &lt;strong&gt;adding a primitive to Windows TLS is a CNG-provider-update problem, not an SChannel-rewrite problem&lt;/strong&gt;. The application surface stays put. IIS does not get rebuilt. &lt;code&gt;SslStream&lt;/code&gt; does not change. The cipher suite negotiated on the wire is whatever the SChannel cipher-suite registry currently exposes; the cipher-suite registry resolves to whatever BCrypt providers are loaded.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Algorithm agility is not a property of TLS-the-protocol. The cipher-suite codepoint is the minor half of the work; the major half is having a substrate that resolves a new algorithm identifier without rebuilding every consumer. CNG&apos;s BCrypt dispatch is what that substrate looks like in Windows. The protocol&apos;s cipher-suite registry is &lt;em&gt;enumerated&lt;/em&gt;; the substrate&apos;s algorithm registry is &lt;em&gt;open&lt;/em&gt;. That asymmetry is the entire game.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;SymCrypt as the parallel track&lt;/h3&gt;

Microsoft&apos;s unified, FIPS 140-validated cryptographic primitive engine. Niels Ferguson began the project in **late 2006** with the first sources committed in February 2007 [@symcrypt-github] -- nearly a decade before Heartbleed. SymCrypt became the primary library for symmetric algorithms starting with Windows 8 and the primary library for all algorithms across Windows since the Windows 10 1703 release in March 2017. Microsoft open-sourced SymCrypt under the MIT license in July 2019 [@symcrypt-github]. Its release-by-release primitive timeline lives in the public CHANGELOG [@symcrypt-changelog].
&lt;p&gt;This timing matters because the original framing many readers carry around -- &quot;Microsoft rewrote its crypto engine after Heartbleed&quot; -- is historically wrong on every axis. SymCrypt predates Heartbleed by seven years [@symcrypt-github]. Heartbleed was an OpenSSL heartbeat-extension bug and did not affect SChannel because SChannel does not implement that code path [@nvd-cve-2014-0160]. The article&apos;s Section 6 treats this conflation in detail. For now, the honest framing is: SymCrypt was the long, quiet maturation of CNG&apos;s primitive layer over a decade, designed by a working Microsoft cryptographer for a substrate already built to accept it.Niels Ferguson&apos;s publicly visible work -- including his co-authorship of &lt;em&gt;Cryptography Engineering&lt;/em&gt; with Bruce Schneier and Tadayoshi Kohno [@schneier-cryptography-engineering] -- is the closest the public has to a primitive-design rationale for what eventually became SymCrypt.&lt;/p&gt;
&lt;p&gt;CNG was Microsoft betting that Windows could keep its Win32 API contract stable while every cryptographic primitive underneath it rotated. The next four sections are the receipts on that bet -- five complete cipher-suite rotations, a parsing-path RCE that almost broke trust in the substrate, and a present-day post-quantum pivot that is the cleanest agility receipt of all.&lt;/p&gt;
&lt;h2&gt;5. Five Generations of Cipher-Suite Rotation, 2009 to 2025&lt;/h2&gt;
&lt;p&gt;Between Windows 7 in 2009 [@ms-learn-tls-cipher-suites-windows-7] and the rolling Windows Server 2022 / 2025 default lists [@ms-learn-tls-cipher-suites-server-2022], Microsoft rotated every primitive in SChannel&apos;s default cipher list at least five times. Not once did IIS, SQL Server, or &lt;code&gt;SslStream&lt;/code&gt; get a source-code change because of it. Those five rotations are the agility receipts.&lt;/p&gt;
&lt;p&gt;The rough shape of the rotations:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generation&lt;/th&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;th&gt;New primitive(s)&lt;/th&gt;
&lt;th&gt;Old primitive(s) retired&lt;/th&gt;
&lt;th&gt;Disablement mechanism&lt;/th&gt;
&lt;th&gt;Cryptographic indictment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;G1&lt;/td&gt;
&lt;td&gt;Win7 / 2008 R2, Oct 2009&lt;/td&gt;
&lt;td&gt;ECDHE key exchange + AES-GCM AEAD&lt;/td&gt;
&lt;td&gt;Static RSA key transport + AES-CBC + HMAC&lt;/td&gt;
&lt;td&gt;New cipher-suite registrations [@ms-learn-tls-cipher-suites-windows-7]&lt;/td&gt;
&lt;td&gt;Lucky13 (2013), BEAST (2011) [@beast-pdf]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G2&lt;/td&gt;
&lt;td&gt;KB2868725, Nov 12 2013 -&amp;gt; off-default 2016&lt;/td&gt;
&lt;td&gt;(none added)&lt;/td&gt;
&lt;td&gt;RC4 stream cipher&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SCH_USE_STRONG_CRYPTO&lt;/code&gt; registry value [@ms-advisory-2868725]&lt;/td&gt;
&lt;td&gt;RC4 NOMORE (75-hour cookie recovery) [@usenix-rc4nomore]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G3&lt;/td&gt;
&lt;td&gt;Win10 v1903, May 2019 Update&lt;/td&gt;
&lt;td&gt;(none added)&lt;/td&gt;
&lt;td&gt;3DES (64-bit block)&lt;/td&gt;
&lt;td&gt;Cipher-suite default-off in cipher list [@ms-learn-cipher-suites-schannel]&lt;/td&gt;
&lt;td&gt;SWEET32 (785 GB / less than 48 h) [@sweet32-info]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G4&lt;/td&gt;
&lt;td&gt;2016-2022&lt;/td&gt;
&lt;td&gt;SHA-256 / SHA-384 handshake signatures&lt;/td&gt;
&lt;td&gt;SHA-1 handshake signatures and SHA-1 trust-store roots&lt;/td&gt;
&lt;td&gt;Microsoft Trusted Root Program distrust events; chain-engine policy&lt;/td&gt;
&lt;td&gt;SHAttered (Feb 2017) [@iacr-eprint-shattered]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G5&lt;/td&gt;
&lt;td&gt;2020-2025&lt;/td&gt;
&lt;td&gt;TLS 1.3 (default-on Win11 / Server 2022)&lt;/td&gt;
&lt;td&gt;TLS 1.0 and TLS 1.1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SCHANNEL\Protocols\TLS 1.0\&amp;lt;role&amp;gt;\DisabledByDefault&lt;/code&gt; [@ms-learn-tls-registry-settings]&lt;/td&gt;
&lt;td&gt;Decade of attack research (BEAST, POODLE, FREAK, Logjam) [@weakdh-logjam]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Each row below adds the engineering detail that the table compresses.&lt;/p&gt;
&lt;h3&gt;G1 -- ECDHE plus AES-GCM (Windows 7 / Server 2008 R2, October 2009)&lt;/h3&gt;

A key-agreement protocol where both parties generate a fresh elliptic-curve key pair for every handshake and exchange public points; the shared secret is never derived from the long-term server certificate&apos;s private key. ECDHE provides **forward secrecy**: compromising the server&apos;s RSA or ECDSA private key tomorrow does not let an adversary decrypt connections recorded today. ECDHE cipher suites first appear in SChannel on Windows 7 / Server 2008 R2 in 2009 [@ms-learn-tls-cipher-suites-windows-7].

A construction that encrypts plaintext and produces an authentication tag in a single operation, with neither output usable in isolation. AEAD ends an entire class of &quot;mac-then-encrypt vs. encrypt-then-mac&quot; padding-oracle bugs (Lucky13, POODLE-style attacks on CBC) by removing the separable padding step entirely. The AEAD framework is introduced in RFC 5246 §6.2.3.3 [@rfc-5246]; AES-GCM is the canonical instantiation on Windows.
&lt;p&gt;The Windows 7 cipher-suite roster enumerates ECDHE-based suites like &lt;code&gt;TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256&lt;/code&gt; and the AES-GCM AEAD suites &lt;code&gt;TLS_RSA_WITH_AES_256_GCM_SHA384&lt;/code&gt; and &lt;code&gt;TLS_RSA_WITH_AES_128_GCM_SHA256&lt;/code&gt; [@ms-learn-tls-cipher-suites-windows-7]. These suites simply did not exist in the SChannel cipher-suite list of XP / Server 2003. Microsoft was able to add them because the CNG substrate, two years old by Windows 7&apos;s RTM, dispatched algorithms by string; the cipher-suite registry just gained new rows that resolved to &lt;code&gt;BCRYPT_ECDH_P256_ALGORITHM&lt;/code&gt; and &lt;code&gt;BCRYPT_AES_ALGORITHM&lt;/code&gt; with the GCM chaining mode set.&lt;/p&gt;
&lt;h3&gt;G2 -- RC4 deprecation (KB2868725, November 12 2013, default-off 2016)&lt;/h3&gt;
&lt;p&gt;In November 2013 Microsoft published Security Advisory 2868725, &quot;Update for Disabling RC4,&quot; which introduced the &lt;code&gt;SCH_USE_STRONG_CRYPTO&lt;/code&gt; flag in the &lt;code&gt;SCHANNEL_CRED&lt;/code&gt; structure and the matching registry mechanism [@ms-advisory-2868725]. The press around the advisory was driven by the BEAST attack (2011) -- whose practical mitigation had been to &lt;em&gt;prefer&lt;/em&gt; RC4 over CBC suites to dodge the CBC implicit-IV bug -- and by mounting attacks against RC4 itself by AlFardan et al. and others.&lt;/p&gt;
&lt;p&gt;The full cryptographic indictment landed at USENIX Security in August 2015: Mathy Vanhoef and Frank Piessens published &quot;All Your Biases Belong to Us: Breaking RC4 in WPA-TKIP and TLS,&quot; demonstrating a 75-hour HTTPS cookie recovery against RC4-secured TLS [@usenix-rc4nomore]. Six months earlier, Andrei Popov of Microsoft Corp. had authored RFC 7465, &quot;Prohibiting RC4 Cipher Suites&quot; [@rfc-7465]. Edge and IE 11 disabled RC4 by default in late 2016; SChannel&apos;s RC4 suites moved to off-by-default on the same trajectory [@ms-learn-cipher-suites-schannel].RFC 7465 (&quot;Prohibiting RC4 Cipher Suites in TLS&quot;) was authored by A. Popov of Microsoft Corp. [@rfc-7465] -- the same engineer whose name is on the current Microsoft Learn SChannel SSP overview page [@ms-learn-schannel-ssp]. Microsoft&apos;s anti-RC4 push was Microsoft-led at the IETF, not just internally.&lt;/p&gt;
&lt;h3&gt;G3 -- 3DES retirement (Windows 10 v1903, May 2019 Update)&lt;/h3&gt;
&lt;p&gt;The cryptographic indictment for 3DES is a textbook example of the &lt;strong&gt;block-cipher birthday bound&lt;/strong&gt;. A 64-bit block cipher reaches a 50% probability of an internal collision after roughly $2^{32}$ encrypted blocks under a single key -- around 32 GB. The SWEET32 paper (Bhargavan and Leurent, ACM CCS 2016) translated that bound into a practical TLS cookie-recovery attack: with 785 GB of induced HTTP traffic over a long-lived 3DES-encrypted connection, an adversary could recover an HTTPS cookie in less than two days [@sweet32-info].&lt;/p&gt;
&lt;p&gt;Microsoft moved 3DES cipher suites to off-by-default starting with &lt;strong&gt;Windows 10 version 1903 (May 2019 Update)&lt;/strong&gt;. The exact pivot is visible in the per-OS Microsoft Learn cipher-suite tables: &lt;code&gt;TLS_RSA_WITH_3DES_EDE_CBC_SHA&lt;/code&gt; appears in the default-enabled list for Windows 10 v1709 and was removed from the default-enabled list for v1903 onward [@ms-learn-cipher-suites-schannel][@ms-learn-tls-cipher-suites-server-2022]. The registry-toggle mechanism is the same &lt;code&gt;SCHANNEL\Ciphers\&amp;lt;algorithm&amp;gt;\Enabled&lt;/code&gt; shape that has been in place since the XP era [@ms-learn-tls-registry-settings]. Crucially, no application changed -- IIS, SQL Server, and &lt;code&gt;SslStream&lt;/code&gt; simply stopped negotiating 3DES because the cipher list no longer offered it.&lt;/p&gt;
&lt;h3&gt;G4 -- SHA-1 sunset (2016 to 2022)&lt;/h3&gt;
&lt;p&gt;SHA-1 deprecation in SChannel was not a single registry flip; it was a coordinated rotation across the &lt;strong&gt;certificate trust pipeline&lt;/strong&gt; (covered in Section 7), the &lt;strong&gt;handshake signature suite&lt;/strong&gt;, and the &lt;strong&gt;trust-store membership&lt;/strong&gt; of root CAs that issued SHA-1 leaves. Two cryptographic indictments did the load-bearing work. The first was &lt;em&gt;protocol-level&lt;/em&gt;: SLOTH (Bhargavan and Leurent, NDSS 2016 [@mitls-sloth][@iacr-eprint-sloth]) showed that an attacker able to compute MD5 or SHA-1 transcript-hash collisions could impersonate one party to the other inside TLS 1.2&apos;s client-authenticated handshake by forging matching &lt;code&gt;CertificateVerify&lt;/code&gt; signatures -- a direct attack on authentication, not on the primitive&apos;s collision resistance in the abstract. The second was &lt;em&gt;primitive-level&lt;/em&gt;: SHAttered (Stevens, Bursztein, Karpman, Albertini, Markov; February 2017 [@iacr-eprint-shattered]) supplied the concrete colliding PDF pair that closed the public debate about SHA-1&apos;s safety margin, published as IACR ePrint 2017/190. Two indictments together -- protocol-level via SLOTH, primitive-level via SHAttered -- is the empirically-correct framing for the SHA-1 retirement timeline.&lt;/p&gt;
&lt;p&gt;For SChannel specifically the rotation was: SHA-256 / SHA-384 handshake signatures for new connections; chain-engine policy stops accepting SHA-1 leaves for &lt;code&gt;id-kp-serverAuth&lt;/code&gt;; and the Microsoft Trusted Root Program distrust events that retired SHA-1 code-signing and TLS certificates from &lt;code&gt;authrootstl.cab&lt;/code&gt; over 2016 to 2022. Section 7 walks the trust pipeline in detail; for the agility argument what matters is that none of these required an &lt;code&gt;SslStream&lt;/code&gt; change.&lt;/p&gt;
&lt;h3&gt;G5 -- TLS 1.0 / 1.1 disablement (2020 to 2025)&lt;/h3&gt;
&lt;p&gt;Microsoft&apos;s rollout used the registry pattern from Section 3. Per-application disablement first -- IE 11, Edge legacy, .NET via &lt;code&gt;ServicePointManager.SecurityProtocol&lt;/code&gt;, individual server roles -- then OS-level defaults in 2024 and 2025 [@ms-learn-tls-registry-settings]. The TLS 1.0 / 1.1 lifecycle is the article&apos;s clearest data point on the difference between &quot;the substrate can rotate&quot; and &quot;the world will move&quot; (Section 11 returns to this).&lt;/p&gt;
&lt;p&gt;The TLS 1.3 side of G5 -- the &lt;em&gt;positive&lt;/em&gt; half of the protocol-version rotation -- shipped default-on in Windows Server 2022 (GA August 2021) and Windows 11 (GA October 5, 2021). Windows 10 and Server 2019 SChannel remain TLS 1.2 only [@ms-learn-tls-cipher-suites-server-2022]. The three TLS 1.3 AEAD suites (&lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt;, &lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt;, &lt;code&gt;TLS_CHACHA20_POLY1305_SHA256&lt;/code&gt;) [@rfc-8446] became the default lineup -- another row of new entries in the cipher-suite registry, with the BCrypt providers behind them already shipping.&lt;/p&gt;
&lt;p&gt;Five rotations, zero application source changes. But one episode from the same era &lt;em&gt;did&lt;/em&gt; break the calm -- a parsing-path remote code execution in SChannel itself, published on Patch Tuesday in November 2014, that the press still routinely confuses with Heartbleed [@ms14-066]. The agility substrate did not protect Windows from that one.&lt;/p&gt;
&lt;h2&gt;6. MS14-066 / WinShock: What Happened, What It Was Not&lt;/h2&gt;
&lt;p&gt;If you searched &quot;SChannel 2014 vulnerability&quot; in late 2014 you got two stories blended together: Heartbleed (April) and the November SChannel RCE everyone called WinShock. They are not the same story. They are not the same vulnerability. They are not even the same vendor. The blending is the single most-misremembered SChannel event, and this article exists in part to set the record straight.&lt;/p&gt;
&lt;h3&gt;What MS14-066 actually was&lt;/h3&gt;
&lt;p&gt;On Patch Tuesday, November 11, 2014, Microsoft published &lt;strong&gt;Security Bulletin MS14-066&lt;/strong&gt; -- &quot;Vulnerability in Schannel Could Allow Remote Code Execution (2992611)&quot; [@ms14-066]. The vulnerability identifier was CVE-2014-6321. The bulletin&apos;s first sentence reads, verbatim:&lt;/p&gt;

This security update resolves a privately reported vulnerability in the Microsoft Secure Channel (Schannel) security package in Windows. The vulnerability could allow remote code execution if an attacker sends specially crafted packets to a Windows server. -- Microsoft Security Bulletin MS14-066, November 11, 2014 [@ms14-066]
&lt;p&gt;The technical character of the bug was a pre-authentication remote code execution in SChannel&apos;s TLS message-parsing path. The NVD record summarises it as &quot;Schannel in Microsoft Windows Server 2003 SP2, Windows Vista SP2, Windows Server 2008 SP2 and R2 SP1, Windows 7 SP1, Windows 8, Windows 8.1, Windows Server 2012 Gold and R2, and Windows RT Gold and 8.1 allows remote attackers to execute arbitrary code via crafted packets&quot; [@nvd-cve-2014-6321]. US-CERT issued Alert TA14-318A confirming the severity and noting the wide platform coverage [@uscert-ta14-318a]; CERT/CC published vulnerability note VU#505120 with the same substance [@certcc-vu505120]. &lt;strong&gt;The bulletin was disclosed under coordinated vulnerability disclosure on the standard Patch Tuesday cadence; IBM X-Force researcher Robert Freeman is publicly credited as the discoverer.&lt;/strong&gt; The &quot;privately reported&quot; phrasing in MS14-066 [@ms14-066] is Microsoft&apos;s standard nomenclature for coordinated-disclosure intake, not a claim that the discovery was internal to Microsoft.&lt;/p&gt;
&lt;h3&gt;What MS14-066 was not&lt;/h3&gt;
&lt;p&gt;It was not Heartbleed. &lt;strong&gt;Heartbleed (CVE-2014-0160), disclosed April 7, 2014, was a flaw in OpenSSL&apos;s TLS Heartbeat extension code path&lt;/strong&gt; [@nvd-cve-2014-0160]. The bug let an attacker over-read OpenSSL process memory by sending a Heartbeat request whose declared payload length exceeded the actual payload. SChannel does not implement the OpenSSL Heartbeat extension; that code simply did not exist in &lt;code&gt;schannel.dll&lt;/code&gt;. Microsoft&apos;s MSRC publicly noted in April 2014 that Microsoft Services were not affected by the Heartbleed vulnerability -- the substance held because the affected codebase was OpenSSL&apos;s, not Microsoft&apos;s [@nvd-cve-2014-0160].The original April 2014 MSRC blog post stating SChannel was unaffected by Heartbleed has migrated and renders only its page chrome today. The substance is independently anchored by the NVD record for CVE-2014-0160, which explicitly scopes the vulnerability to OpenSSL 1.0.1 through 1.0.1f.&lt;/p&gt;
&lt;p&gt;It was not &quot;silently patched,&quot; at least not at the headline level. CVE-2014-6321 had a public Patch Tuesday bulletin, contemporary Krebs and NVD coverage, US-CERT and CERT/CC alerts, and proof-of-concept walkthroughs from BeyondTrust and Security Sift within months [@certcc-vu505120][@uscert-ta14-318a]. The &quot;silently patched&quot; framing in the press is the residue of a &lt;em&gt;real but narrower&lt;/em&gt; fact: the same KB shipped additional Schannel hardening fixes that were not separately bulletined.The &quot;silently patched&quot; framing of MS14-066 is itself the residue of a real fact -- the November 11, 2014 KB included Schannel hardening fixes that were not separately bulletined. The headline CVE itself was very much public, and the discovery is publicly credited to IBM X-Force researcher Robert Freeman under coordinated vulnerability disclosure. This article does not assign specific CVE IDs to the &lt;em&gt;bundled&lt;/em&gt; hardening extras, in line with the project&apos;s premise-audit discipline.&lt;/p&gt;
&lt;h3&gt;What it occasioned&lt;/h3&gt;
&lt;p&gt;Three lasting effects of MS14-066 are worth naming.&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;the cipher-suite expansion in the same KB&lt;/strong&gt;. The patch bundled new TLS 1.2 cipher suites (the ECDHE-RSA suites that Windows 7 and Server 2008 R2 had partially supported, broadened across the entire then-supported family). Some operators were caught off guard by the new lineup; the registry-toggle pattern from Section 3 was what got them out of the bind.&lt;/p&gt;
&lt;p&gt;Second, &lt;strong&gt;a measurable uptick in external SChannel fuzzing&lt;/strong&gt;. After 2014, the public TLS-stack-testing community treated SChannel as a first-class target, not as a closed-source black box no one could meaningfully probe. The most visible artifact is Hubert Kario&apos;s TLS-Fuzzer at Red Hat -- a test suite that, in the project&apos;s own framing, &quot;doesn&apos;t check only that the system under test didn&apos;t crash, it checks that it returned correct error messages&quot; [@tlsfuzzer-github]. Section 11 returns to TLS-Fuzzer as the closest public substitute for a behavioural specification of SChannel.&lt;/p&gt;
&lt;p&gt;Third, the lesson the substrate could not absorb: &lt;strong&gt;algorithm-agility does not extend to the parsing path&lt;/strong&gt;. The wire-format state machine has to be correct because no provider model can fix a bug in &lt;code&gt;schannel.dll&lt;/code&gt; itself. CNG could rotate primitives without rewriting SChannel; CNG could not rotate SChannel&apos;s TLS message parser. That asymmetry is structural and remains true today.&lt;/p&gt;
&lt;h3&gt;What it was not, part two: not the trigger for SymCrypt&lt;/h3&gt;
&lt;p&gt;Some narratives connect MS14-066 to a &quot;SChannel rewrite&quot; or a &quot;FIPS rewrite&quot; project that followed. The dates do not support either framing. SymCrypt was started by Niels Ferguson in &lt;strong&gt;late 2006&lt;/strong&gt;, with the first sources committed in February 2007 [@symcrypt-github] -- seven years before Heartbleed and eight years before MS14-066. SymCrypt became the primary library for symmetric algorithms with Windows 8 (October 2012, before MS14-066) and the primary library for all algorithms across Windows starting with the Windows 10 1703 release in March 2017. Open-sourcing followed under the MIT license in July 2019 [@symcrypt-github]. The honest story is that SymCrypt was the maturation of CNG&apos;s primitive layer over a decade; it had no causal relationship to either 2014 disclosure.&lt;/p&gt;

This article refuses to assert any causal link between Heartbleed and SymCrypt because the timeline does not support it. SymCrypt began in late 2006; Heartbleed was disclosed in April 2014. SymCrypt&apos;s role as the Windows-wide primary crypto library lands with Windows 10 1703 in March 2017 [@symcrypt-github]. Conflations of this kind are how the security-pop-press version of history overwrites the engineering version. The agility argument is stronger, not weaker, when the actual causal chains are preserved.
&lt;p&gt;MS14-066 taught Microsoft that the substrate&apos;s algorithm-agility property does not extend to the parsing path -- the wire-format state machine has to be correct because no provider model can fix a bug in &lt;code&gt;schannel.dll&lt;/code&gt; itself. The next section turns to the &lt;em&gt;other&lt;/em&gt; load-bearing path: not the bytes on the wire, but the certificate the server presents to authenticate.&lt;/p&gt;
&lt;h2&gt;7. The Certificate-Validation Pipeline: &lt;code&gt;CertGetCertificateChain&lt;/code&gt;, OCSP, and the Microsoft Trusted Root Program&lt;/h2&gt;
&lt;p&gt;The other half of any TLS handshake is &lt;strong&gt;trust&lt;/strong&gt;. Bytes can be encrypted with the strongest AEAD in the SymCrypt CHANGELOG and the handshake can use a quantum-resistant key exchange -- and the whole exchange still means nothing if the certificate the server presents traces back to an attacker-controlled CA. On Windows, that whole question routes through one API: &lt;code&gt;CertGetCertificateChain&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;The chain engine&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;CertGetCertificateChain&lt;/code&gt; walks from leaf to trusted root using Authority Key Identifier / Subject matching, fetches any missing intermediates via the certificate&apos;s Authority Information Access (AIA) &lt;code&gt;caIssuers&lt;/code&gt; URL, and resolves against the local Microsoft Trusted Root Store. The store itself is kept current through the &lt;code&gt;crypt32.dll&lt;/code&gt; auto-update mechanism, which downloads a signed &lt;code&gt;authrootstl.cab&lt;/code&gt; periodically and updates the trust list in place.&lt;/p&gt;
&lt;p&gt;Per-certificate checks follow the X.509 PKI profile (RFC 5280, May 2008) [@rfc-5280]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Signature verification&lt;/strong&gt; -- each cert is signed by the next-up cert&apos;s private key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validity&lt;/strong&gt; -- &lt;code&gt;notBefore&lt;/code&gt; / &lt;code&gt;notAfter&lt;/code&gt; within the current time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key Usage and Extended Key Usage&lt;/strong&gt; -- the leaf must include &lt;code&gt;id-kp-serverAuth&lt;/code&gt; (&lt;code&gt;1.3.6.1.5.5.7.3.1&lt;/code&gt;) for a TLS server presentation, and the chain&apos;s intermediates must permit &lt;code&gt;serverAuth&lt;/code&gt; in their EKU constraints if they declare any.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Constraints&lt;/strong&gt; -- non-leaf certs must have &lt;code&gt;cA=TRUE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Name Constraints&lt;/strong&gt; -- per RFC 5280 §4.2.1.10, intermediates may declare &lt;code&gt;permittedSubtrees&lt;/code&gt; and &lt;code&gt;excludedSubtrees&lt;/code&gt; over DNS names, IP ranges, and other name forms; the chain engine enforces these against the leaf&apos;s SAN.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Revocation&lt;/strong&gt; -- per-cert, against the chosen revocation source.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;CertVerifyCertificateChainPolicy&lt;/code&gt; then layers protocol-specific overlays on top of that purely structural validation. The most important for TLS is &lt;code&gt;CERT_CHAIN_POLICY_SSL&lt;/code&gt;, which adds the SNI / SAN hostname match and TLS-specific server-auth constraints.&lt;/p&gt;

flowchart TD
    A[&quot;Leaf certificate from TLS handshake&quot;] --&amp;gt; B[&quot;Chain engine -- CertGetCertificateChain&quot;]
    B --&amp;gt; C{&quot;Path build via AKI / SKI matching, AIA caIssuers fetch&quot;}
    C --&amp;gt; D[&quot;Per-cert structural checks (RFC 5280)&quot;]
    D --&amp;gt; E{&quot;Revocation source&quot;}
    E --&amp;gt; F[CRL distribution point]
    E --&amp;gt; G[OCSP responder]
    E --&amp;gt; H[OCSP stapled response]
    D --&amp;gt; I[&quot;CertVerifyCertificateChainPolicy&quot;]
    I --&amp;gt; J{&quot;CERT_CHAIN_POLICY_SSL -- SNI / SAN match, serverAuth EKU&quot;}
    J --&amp;gt; K[Chain valid for TLS server]
    F --&amp;gt; I
    G --&amp;gt; I
    H --&amp;gt; I
&lt;h3&gt;Revocation: CRL, OCSP, OCSP stapling&lt;/h3&gt;

The **Online Certificate Status Protocol** (RFC 6960) lets a client ask the issuing CA&apos;s OCSP responder whether a specific certificate is revoked, by serial number [@rfc-6960]. Plain OCSP is a separate request to the CA on every connection, which leaks visited hostnames to the CA and adds latency. **OCSP stapling** lets the server fetch a fresh signed OCSP response on a schedule and &quot;staple&quot; it into the TLS handshake via the `status_request` extension -- the client gets the same revocation proof without the side channel. SChannel consumes stapled OCSP responses through the `status_request` extension (RFC 6066 §8 for TLS 1.2, RFC 8446 §4.4.2.1 for TLS 1.3 [@rfc-8446]) and feeds the result into the chain engine.
&lt;p&gt;A practical SChannel deployment combines CRL fetching, OCSP, and OCSP stapling: stapling preferred when present, OCSP fallback when not, CRL as the long-tail safety net. IIS&apos;s stapling support is on by default in modern releases; turning it off is the wrong default for any internet-facing endpoint.&lt;/p&gt;
&lt;h3&gt;The Microsoft Trusted Root Program and the CCADB&lt;/h3&gt;
&lt;p&gt;SChannel&apos;s trust posture inherits the Microsoft Trusted Root Program&apos;s membership decisions. Microsoft does not run the trust program in isolation. It participates in the &lt;strong&gt;Common CA Database (CCADB)&lt;/strong&gt; alongside Mozilla, Google, and Apple, sharing root inclusion / removal / audit data across the major root stores [@ccadb-resources]. The CCADB Resources page lists the public extractions (Microsoft&apos;s TLS roots, Mozilla&apos;s TLS roots, code-signing roots, S/MIME roots) and the program-specific report URLs.&lt;/p&gt;
&lt;p&gt;The governance flow is documented end-to-end on the Microsoft Trusted Root Program program-requirements page [@ms-trusted-root-program-requirements]. Membership requires annual WebTrust or ETSI EN 319 411 audits, full CCADB disclosure of the PKI hierarchy, and adherence to the technical requirements (minimum key sizes, signature-algorithm policy, extension constraints, name-form profiles). Distrust decisions can be triggered by (a) CCADB-coordinated cross-vendor consensus where Microsoft acts alongside Mozilla, Apple, and Google; (b) unilateral Microsoft action when the program judges a CA below the bar; or (c) audit-failure findings that fail to remediate inside an agreed window.&lt;/p&gt;
&lt;p&gt;Propagation to Windows clients goes through two signed trust lists distributed via the Automatic Root Update mechanism: &lt;strong&gt;&lt;code&gt;authrootstl.cab&lt;/code&gt;&lt;/strong&gt; carries the currently-trusted roots together with per-EKU enablement bits, and &lt;strong&gt;&lt;code&gt;disallowedcertstl.cab&lt;/code&gt;&lt;/strong&gt; is the explicit untrust list. Both are fetched by &lt;code&gt;crypt32.dll&lt;/code&gt; from &lt;code&gt;http://ctldl.windowsupdate.com/...&lt;/code&gt; on a periodic schedule and consumed by the chain engine on its next chain build. The SChannel SSP itself does not maintain a separate trust list; it inherits whatever &lt;code&gt;CertGetCertificateChain&lt;/code&gt; resolves against the auto-updated stores.&lt;/p&gt;

flowchart TB
    A[CA submits audit, CCADB disclosure, technical compliance] --&amp;gt; B[Microsoft Trusted Root Program review]
    B --&amp;gt; C{&quot;Decision -- include, distrust, NotBefore-date schedule&quot;}
    C --&amp;gt; D[CCADB cross-vendor coordination -- Mozilla, Apple, Google]
    C --&amp;gt; E[authrootstl.cab updates]
    C --&amp;gt; F[disallowedcertstl.cab updates]
    E --&amp;gt; G[ctldl.windowsupdate.com distribution]
    F --&amp;gt; G
    G --&amp;gt; H[crypt32.dll Automatic Root Update on client]
    H --&amp;gt; I[CertGetCertificateChain consults updated stores]
    I --&amp;gt; J[SChannel SSP handshake trust decision]
&lt;h3&gt;Two worked examples: DigiNotar (2011) and Symantec (2018)&lt;/h3&gt;
&lt;p&gt;The MTRP governance flow looks abstract until two real distrust events make it concrete.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DigiNotar -- August / September 2011 -- panic-mode revocation.&lt;/strong&gt; Microsoft Security Advisory 2607712 (&quot;Fraudulent Digital Certificates Could Allow Spoofing&quot;) was published on August 29, 2011 and updated through September 19 to version 5.0 [@ms-advisory-2607712-diginotar]. The Dutch CA DigiNotar&apos;s signing infrastructure had been breached by an attacker who issued fraudulent certificates for &lt;code&gt;*.google.com&lt;/code&gt; and other high-value names. Microsoft, Mozilla, Apple, and Google removed DigiNotar&apos;s roots from their trust stores within days. The Microsoft-side propagation pushed the DigiNotar Root CA out of &lt;code&gt;authrootstl.cab&lt;/code&gt; and added the relevant entries to &lt;code&gt;disallowedcertstl.cab&lt;/code&gt;; clients on the Automatic Root Update pipeline picked up the change within the next refresh cycle. SChannel&apos;s chain engine then refused to validate any leaf signed under the DigiNotar hierarchy -- not because SChannel changed, but because the trust store it consults changed underneath it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Symantec deprecation -- October 2018 -- planned per-NotBefore-date schedule.&lt;/strong&gt; The Symantec distrust is the cleanest published example of how a CCADB-coordinated &lt;em&gt;planned&lt;/em&gt; deprecation differs from a &lt;em&gt;panic-mode&lt;/em&gt; revocation. Microsoft&apos;s October 4, 2018 Security Blog post documents the four-vendor (Microsoft, Mozilla, Apple, Google) coordinated schedule, keyed on the certificate&apos;s &lt;code&gt;NotBefore&lt;/code&gt; date rather than on the root itself: per the per-root table in the blog post, the relevant cut-overs were September 30, 2018; January 31, 2019; and January 1, 2020 [@ms-blog-symantec-distrust]. Certificates &lt;em&gt;issued before&lt;/em&gt; the per-root NotBefore date stayed trusted to their natural expiration; certificates &lt;em&gt;issued after&lt;/em&gt; were rejected. The mechanism on the SChannel side is unchanged from DigiNotar -- the chain engine reads the updated trust posture from &lt;code&gt;authrootstl.cab&lt;/code&gt; / &lt;code&gt;disallowedcertstl.cab&lt;/code&gt; and applies it on the next chain build -- but the &lt;em&gt;operational character&lt;/em&gt; is completely different: a years-long planned phase-out instead of a week-long emergency cleanup.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A panic-mode distrust (DigiNotar) removes a root outright and propagates over days. A planned distrust (Symantec) uses NotBefore dates to grandfather pre-existing certificates while rejecting new ones, propagates over months to years, and gives the broader industry time to migrate. Both flow through the same &lt;code&gt;authrootstl.cab&lt;/code&gt; / &lt;code&gt;disallowedcertstl.cab&lt;/code&gt; plumbing. The governance subtlety lives in &lt;em&gt;which kind&lt;/em&gt; of distrust the program issues for a given CA&apos;s circumstances.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Enterprise observability: CAPI2/Operational event IDs&lt;/h3&gt;
&lt;p&gt;The governance flow ends at the operator&apos;s host -- but only if the operator can see it land. The Microsoft Learn troubleshooting article on the May 24, 2022 removal of the U.S. Federal Common Policy CA &quot;G1&quot; root carries the canonical observability recipe [@ms-learn-fcpca-removal]. On any Windows host you can enable the per-event tracing channel with &lt;code&gt;wevtutil sl Microsoft-Windows-CAPI2/Operational /e:true&lt;/code&gt; and then watch the Event Viewer under &lt;code&gt;Applications and Services Logs &amp;gt; Microsoft &amp;gt; Windows &amp;gt; CAPI2 &amp;gt; Operational&lt;/code&gt; for the chain-engine events that the FCPCA-removal article enumerates verbatim [@ms-learn-fcpca-removal]: &lt;strong&gt;Event ID 90&lt;/strong&gt; logs every certificate consulted during chain building, &lt;strong&gt;Event ID 11&lt;/strong&gt; records chain-build failures, &lt;strong&gt;Event ID 30&lt;/strong&gt; records SSL or NTAuth policy-layer failures, &lt;strong&gt;Events 40-43&lt;/strong&gt; show stored CRLs and AIA paths, and &lt;strong&gt;Events 50-53&lt;/strong&gt; show network CRL accesses. The same article documents the empirical post-distrust propagation window in plain language: &quot;Applications and operations that depend on the &apos;G1&apos; root certificate will fail one to seven days after they receive the root certificate update.&quot; That one-to-seven-day window is the realistic latency budget between an MTRP distrust event landing in &lt;code&gt;authrootstl.cab&lt;/code&gt; and a given Windows host actually applying it -- a fingerprint operators can validate per host, not just per the rollout calendar.&lt;/p&gt;
&lt;p&gt;The PowerShell complement is brief and worth keeping in the muscle memory: &lt;code&gt;Get-ChildItem Cert:\LocalMachine\AuthRoot&lt;/code&gt; enumerates the currently-trusted roots; &lt;code&gt;Get-ChildItem Cert:\LocalMachine\Disallowed&lt;/code&gt; enumerates the disallowed store; both reflect whatever the last &lt;code&gt;crypt32.dll&lt;/code&gt; Automatic Root Update cycle left in place.&lt;/p&gt;
&lt;h3&gt;A cautionary tale: CVE-2020-0601, &quot;Curveball&quot;&lt;/h3&gt;
&lt;p&gt;In January 2020 the NSA disclosed a chain-engine spoofing vulnerability in &lt;code&gt;crypt32.dll&lt;/code&gt;&apos;s ECC certificate validation [@nvd-cve-2020-0601][@nsa-curveball-alternative]. The bug let an attacker craft a fraudulent ECC certificate that the Windows chain engine would treat as having been signed by a trusted root, by failing to fully verify the curve parameters against the cached trusted root&apos;s curve. Curveball is strictly a &lt;code&gt;crypt32.dll&lt;/code&gt; bug, not a SChannel SSP bug -- but it shaped the SChannel posture in two ways. First, it demonstrated that the chain engine and the SSP are &lt;em&gt;equally&lt;/em&gt; load-bearing for &quot;is this TLS connection trustworthy?&quot; Second, it was the most prominent example of the NSA disclosing a Windows vulnerability via the regular MSRC channel rather than hoarding it. Microsoft&apos;s January 2020 Patch Tuesday cycle addressed CVE-2020-0601 ahead of any public proof-of-concept.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The agility property the rest of this article celebrates is a property of CNG and SChannel. The trust pipeline -- &lt;code&gt;CertGetCertificateChain&lt;/code&gt;, &lt;code&gt;CertVerifyCertificateChainPolicy&lt;/code&gt;, the trust-store update mechanism in &lt;code&gt;crypt32.dll&lt;/code&gt; -- is a parallel concern. A perfectly executed TLS 1.3 handshake against a trusted-looking certificate that is actually fraudulent is still a compromise. Curveball is the canonical reminder that audit posture for SChannel-served endpoints has to cover both halves.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Certificate validation is the other axis on which SChannel has had to evolve. With the substrate (Sections 4 through 6) and the trust pipeline (this section) both stabilised, the article now turns to what &quot;modern SChannel&quot; actually looks like in the field -- TLS 1.3 on by default, TPM-backed server keys available for compliance scenarios, and the LSA-protection moat that makes session-key extraction harder than it used to be.&lt;/p&gt;
&lt;h2&gt;8. Modern SChannel: TLS 1.3, CredSSP for RDP, TPM-Backed Keys, and the LSASS Moat&lt;/h2&gt;
&lt;p&gt;By mid-2026 a default Windows 11 or Server 2022 / 2025 box is doing things its 2019 equivalent could not. TLS 1.3 is on. CredSSP wraps the RDP credential-delegation path inside a SChannel-protected TLS tunnel [@ms-cssp-landing]. The TPM is available as a key custodian. LSASS is a Protected Process; on most newer Windows 11 builds, Credential Guard is on by default. These are not four independent stories; they are four layers of the same defence-in-depth posture for the modern SChannel-served TLS endpoint.&lt;/p&gt;
&lt;h3&gt;TLS 1.3 in SChannel&lt;/h3&gt;
&lt;p&gt;RFC 8446 (Eric Rescorla, Mozilla, August 2018) [@rfc-8446] is the protocol generation that SChannel finally ships default-on in Windows Server 2022 (GA August 2021) and Windows 11 (GA October 5, 2021). Windows 10 and Windows Server 2019 SChannel remain TLS 1.2 only -- a fact worth naming because it is the most common cause of confusion in mixed-version Windows fleets [@ms-learn-tls-cipher-suites-server-2022].&lt;/p&gt;
&lt;p&gt;What changed at the wire-format level matters less for SChannel than how cleanly the changes mapped through CNG. TLS 1.3 shrank the cipher-suite menu to three AEAD suites: &lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt;, &lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt;, and &lt;code&gt;TLS_CHACHA20_POLY1305_SHA256&lt;/code&gt; [@rfc-8446]. The key-share namespace separated from the cipher-suite namespace -- &lt;code&gt;supported_groups&lt;/code&gt; (X25519, secp256r1, secp384r1, and now &lt;code&gt;X25519MLKEM768&lt;/code&gt;) is an independent extension from &lt;code&gt;cipher_suites&lt;/code&gt;. The handshake collapsed to one round trip.The 0-RTT (early data) feature of TLS 1.3 trades a round trip for replay-resistance complexity. SChannel&apos;s posture on 0-RTT is conservative: clients can request it, servers default to off unless explicitly opted in, and the documentation flags the replay-protection trade-offs.&lt;/p&gt;
&lt;p&gt;The downgrade-resistance sentinel in &lt;code&gt;ServerHello.random&lt;/code&gt; (RFC 8446 §4.1.3) is worth a beat. A TLS 1.3 server that, for whatever reason, is negotiated down to TLS 1.2 or below by middlebox interference fills the last eight bytes of its &lt;code&gt;ServerHello.random&lt;/code&gt; with one of two well-known sentinels (&lt;code&gt;44 4F 57 4E 47 52 44 01&lt;/code&gt; for &quot;downgraded from 1.3 to 1.2&quot;; &lt;code&gt;44 4F 57 4E 47 52 44 00&lt;/code&gt; for &quot;downgraded from 1.3 to 1.1 or earlier&quot;). A genuinely TLS 1.3-capable client checks for the sentinel after the handshake and aborts on mismatch. This puts the active-downgrade-attack envelope inside TLS 1.3 at a much narrower place than it was in TLS 1.2.&lt;/p&gt;

sequenceDiagram
    participant App as Application
    participant SC as SChannel SSP
    participant CNG as BCrypt / NCrypt
    participant Peer as Remote endpoint&lt;pre&gt;&lt;code&gt;App-&amp;gt;&amp;gt;SC: AcquireCredentialsHandle (server cert, key handle)
App-&amp;gt;&amp;gt;SC: InitializeSecurityContext (first call)
SC-&amp;gt;&amp;gt;CNG: BCrypt ECDH or MLKEM key share
SC-&amp;gt;&amp;gt;Peer: ClientHello (cipher_suites, supported_groups, key_share)
Peer--&amp;gt;&amp;gt;SC: ServerHello, EncryptedExtensions, Certificate, CertVerify, Finished
SC-&amp;gt;&amp;gt;CNG: NCryptSignHash or NCrypt key derive
SC-&amp;gt;&amp;gt;App: SECBUFFER tokens, then SEC_E_OK
App-&amp;gt;&amp;gt;SC: EncryptMessage and DecryptMessage on every record
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;CredSSP and the Remote Desktop NLA path&lt;/h3&gt;

The SSPI provider in `credssp.dll` that securely delegates Windows credentials from a client to a target server inside a TLS-protected tunnel. CredSSP is the SSP that backs Remote Desktop Network Level Authentication (RDP NLA): it wraps a SChannel TLS handshake, tunnels an SPNEGO / Kerberos / NTLM authentication inside that tunnel, performs a channel-binding hash exchange, and finally transmits the user&apos;s credential material to the destination encrypted under the SSPI session key [@ms-cssp-landing][@ms-cssp-glossary].
&lt;p&gt;The Microsoft Open Specifications page for the &lt;strong&gt;Credential Security Support Provider Protocol&lt;/strong&gt; ([MS-CSSP], version 21.0, April 2024) [@ms-cssp-landing] defines the protocol that backs Remote Desktop NLA. CredSSP is not a TLS protocol of its own; it is an SSP that &lt;em&gt;uses&lt;/em&gt; SChannel as its transport. The relationship is structural -- CredSSP is one of the most consequential &lt;em&gt;consumers&lt;/em&gt; of SChannel inside Windows, and almost every RDP session opened against a modern Windows host runs the CredSSP-over-SChannel sequence before the RDP video stream even starts.&lt;/p&gt;
&lt;p&gt;The five-step CredSSP-over-TLS sequence per the open-spec &quot;Processing Events and Sequencing Rules&quot; page [@ms-cssp-sequencing]:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;TLS handshake&lt;/strong&gt;. The CredSSP client and CredSSP server complete the SChannel TLS handshake; only the server presents a certificate, so the TLS-layer client is anonymous. After this step, all subsequent CredSSP messages are encrypted by the TLS channel. The MS-CSSP spec is explicit that &quot;the CredSSP Protocol does not extend the TLS wire protocol&quot; and that &quot;TLS session resumption is not supported&quot; [@ms-cssp-sequencing].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SPNEGO / Kerberos / NTLM tunnelled inside TLS&lt;/strong&gt;. Authentication tokens are carried in the &lt;code&gt;negoTokens&lt;/code&gt; field of the protocol&apos;s &lt;code&gt;TSRequest&lt;/code&gt; ASN.1 structure. The negotiation is performed by the SSPI Negotiate provider, which usually selects Kerberos when the client is domain-joined and falls back to NTLM otherwise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Public-key (channel-binding) hash exchange&lt;/strong&gt;. This is the post-CVE-2018-0886 mechanism. The client computes a SHA-256 hash over a fixed magic string concatenated with a nonce and the server&apos;s &lt;code&gt;SubjectPublicKey&lt;/code&gt;, encrypts that hash under the SSPI session key established in step 2, and sends it in the &lt;code&gt;pubKeyAuth&lt;/code&gt; field of &lt;code&gt;TSRequest&lt;/code&gt;. The earlier (v2 / v3 / v4) &quot;encrypt the public key + 1&quot; scheme that was broken by CVE-2018-0886 has been replaced by this channel-binding hash for protocol versions 5 and 6 of CredSSP.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-side hash response with the server magic&lt;/strong&gt;. The server computes its own version of the hash (using a different fixed magic string for the server-to-client direction), encrypts it under the session key, and returns it in its own &lt;code&gt;pubKeyAuth&lt;/code&gt;. Both sides have now proven they hold the same session key bound to the same server public key, which closes a class of man-in-the-middle attacks against the inner authentication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encrypted credential transfer in &lt;code&gt;authInfo&lt;/code&gt;&lt;/strong&gt;. The credentials themselves -- a &lt;code&gt;TSPasswordCreds&lt;/code&gt;, &lt;code&gt;TSSmartCardCreds&lt;/code&gt;, or &lt;code&gt;TSRemoteGuardCreds&lt;/code&gt; structure depending on the chosen logon style -- are encrypted under the SSPI session key and transmitted in the &lt;code&gt;authInfo&lt;/code&gt; field. The destination decrypts them inside &lt;code&gt;lsass.exe&lt;/code&gt; (a PPL-protected process when RunAsPPL is enabled, see below), and the operating system then uses them to log the user on.&lt;/li&gt;
&lt;/ol&gt;

sequenceDiagram
    participant Client as RDP client
    participant Server as RDP server
    Note over Client,Server: Step 1 -- SChannel TLS handshake, server cert only, client anonymous at TLS layer
    Client-&amp;gt;&amp;gt;Server: ClientHello
    Server--&amp;gt;&amp;gt;Client: ServerHello, Certificate, ServerHelloDone (TLS 1.2) or one-RTT TLS 1.3 equivalent
    Client-&amp;gt;&amp;gt;Server: Finished -- TLS tunnel up
    Note over Client,Server: Step 2 -- SPNEGO Kerberos or NTLM tokens inside TSRequest.negoTokens, all inside TLS
    Client-&amp;gt;&amp;gt;Server: TSRequest with negoTokens (Kerberos AP-REQ or NTLM Type 1)
    Server--&amp;gt;&amp;gt;Client: TSRequest with negoTokens (Kerberos AP-REP or NTLM Type 2 then 3)
    Note over Client,Server: Step 3 -- channel-binding hash, client side (replaces broken pre-CVE-2018-0886 scheme)
    Client-&amp;gt;&amp;gt;Server: TSRequest.pubKeyAuth -- E(sessionKey, SHA256(client-magic, nonce, server SubjectPublicKey))
    Note over Client,Server: Step 4 -- server-side hash response with server-magic
    Server--&amp;gt;&amp;gt;Client: TSRequest.pubKeyAuth -- E(sessionKey, SHA256(server-magic, nonce, server SubjectPublicKey))
    Note over Client,Server: Step 5 -- encrypted credentials in TSRequest.authInfo
    Client-&amp;gt;&amp;gt;Server: TSRequest.authInfo -- E(sessionKey, TSPasswordCreds or TSSmartCardCreds or TSRemoteGuardCreds)
    Note over Server: lsass.exe decrypts, logs the user on
&lt;p&gt;The NLA threat-model framing per the archived Server 2008 R2 TechNet content is worth quoting because it captures what NLA actually buys [@ms-archive-nla]. NLA forces user authentication &lt;em&gt;before&lt;/em&gt; RDP session resources are allocated: &quot;It requires fewer remote computer resources initially. The remote computer uses a limited number of resources before authenticating the user, rather than starting a full remote desktop connection as in previous versions. It can help provide better security by reducing the risk of denial-of-service attacks.&quot; The two concrete payoffs are pre-auth DoS resistance and pre-auth RDP-codepath RCE mitigation. &lt;strong&gt;BlueKeep (CVE-2019-0708)&lt;/strong&gt; and &lt;strong&gt;DejaBlue (CVE-2019-1181 / 1182)&lt;/strong&gt; would each have been substantially harder to exploit on NLA-enabled hosts because the vulnerable RDP code paths sit &lt;em&gt;behind&lt;/em&gt; the NLA gate. NLA has been on by default for RDP Session Hosts since Windows Server 2012 R2.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A naive TLS-only deployment authenticates the &lt;em&gt;server&lt;/em&gt; to the &lt;em&gt;client&lt;/em&gt; via the server certificate, and authenticates the &lt;em&gt;user&lt;/em&gt; in plaintext above TLS. CredSSP adds a second layer: the user&apos;s authentication runs inside the TLS tunnel via SPNEGO / Kerberos / NTLM, and the user&apos;s credentials -- if delegated at all -- are encrypted under a session key that is channel-bound to the server&apos;s public key. With Remote Credential Guard (&lt;code&gt;TSRemoteGuardCreds&lt;/code&gt;), the destination&apos;s plaintext-credential exposure can be reduced to zero -- the destination receives only a service ticket usable for the session, not a reusable password hash.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;TPM-backed server keys via the Microsoft Platform Crypto Provider&lt;/h3&gt;
&lt;p&gt;The Microsoft Platform Crypto Provider (PCP) is a KSP that stores private keys non-exportable inside TPM 2.0. For an IIS or &lt;code&gt;SslStream&lt;/code&gt; server, switching to a PCP-backed certificate means the certificate&apos;s private key never resides in software memory; CertificateVerify signing during the handshake dispatches through &lt;code&gt;NCryptSignHash&lt;/code&gt; to PCP to &lt;code&gt;TPM2_Sign&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Two caveats need stating plainly. First, PCP-backed key operations are slower than software-backed key operations -- TPM 2.0 ECDSA / RSA signing latency is in the tens of milliseconds, which is a hard cap on handshake throughput. A high-volume edge IIS workload cannot meet its handshake-rate SLA with TPM-backed keys. Second, &lt;strong&gt;production prevalence of PCP-backed server keys remains low outside specific compliance scenarios&lt;/strong&gt;. The capability is shipping; the typical pattern is software-backed keys at the edge and TPM-backed keys for long-lived service-identity certificates where the latency does not dominate.TPM 2.0 signing latency is the ceiling for TPM-backed TLS handshake throughput. A high-volume IIS edge cannot meet handshake-rate SLAs with TPM-backed keys; that is why the typical pattern is software-backed keys at the edge and TPM-backed keys for service identity at lower call rates.&lt;/p&gt;
&lt;h3&gt;LSA Protection (RunAsPPL) and Credential Guard&lt;/h3&gt;

A Windows process-protection lattice where a &quot;protected&quot; process can be opened for certain rights only by callers whose protection level is greater than or equal to the target&apos;s. When LSASS runs as a PPL (via `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL`), a non-PPL caller&apos;s `OpenProcess(LSASS, PROCESS_VM_READ, ...)` returns `ERROR_ACCESS_DENIED`. PPL is a same-privilege gate: it operates entirely inside Virtual Trust Level 0 (VTL0), the normal kernel/user world [@ms-learn-lsa-protection][@itm4n-runasppl].
&lt;p&gt;LSASS holds the cleartext session keys SChannel derives for each active TLS connection. Historically Mimikatz&apos;s &lt;code&gt;sekurlsa::schannel&lt;/code&gt; command read those keys directly out of LSASS memory after a debug-privilege &lt;code&gt;OpenProcess&lt;/code&gt;. Once RunAsPPL is enforced, the read fails: a non-PPL Mimikatz cannot open LSASS for memory read [@ms-learn-lsa-protection].&lt;/p&gt;
&lt;p&gt;Clément Labro&apos;s RunAsPPL analysis (&lt;code&gt;itm4n&lt;/code&gt;) is the canonical practitioner&apos;s text on the gotchas [@itm4n-runasppl]. The single most important framing point Labro makes is the disambiguation between PPL and Credential Guard:&lt;/p&gt;

When it comes to protecting against credentials theft on Windows, enabling LSA Protection (a.k.a. RunAsPPL) on LSASS may be considered as the very first recommendation to implement... Credential Guard and LSA Protection are actually complementary. -- Clément Labro, *Do You Really Know About LSA Protection (RunAsPPL)?* [@itm4n-runasppl]
&lt;p&gt;The disambiguation matters because the two mechanisms operate at different layers. &lt;strong&gt;PPL is a same-privilege gate inside VTL0.&lt;/strong&gt; &lt;strong&gt;Credential Guard moves credential material into the LSAIso trustlet at VTL1&lt;/strong&gt;, behind the VBS / Hyper-V boundary -- a cross-privilege isolation that PPL cannot provide [@ms-learn-credential-guard]. The misconception that Credential Guard alone defeats &lt;code&gt;mimikatz sekurlsa::schannel&lt;/code&gt; is one of the most common operator errors in this space. They stack. They are not substitutes.&lt;/p&gt;

flowchart TB
    subgraph VTL0[&quot;VTL0 -- Normal World&quot;]
        subgraph User[&quot;User mode&quot;]
            App[&quot;Mimikatz / arbitrary code -- non-PPL&quot;]
        end
        subgraph Kern[&quot;Kernel mode (NT kernel)&quot;]
            LSASS[&quot;LSASS -- PPL when RunAsPPL=1&quot;]
            SCh[schannel.dll loaded in LSASS]
        end
    end
    subgraph VTL1[&quot;VTL1 -- Isolated User Mode (VBS)&quot;]
        LSAIso[&quot;LSAIso trustlet -- Credential Guard&quot;]
    end
    App -. &quot;OpenProcess(LSASS, VM_READ) -- denied when PPL on&quot; .-&amp;gt; LSASS
    LSASS -. &quot;RPC to LSAIso for credential ops&quot; .-&amp;gt; LSAIso
    SCh --&amp;gt; LSASS
&lt;p&gt;The last open question on RunAsPPL is whether the protection itself is bypassable. The honest answer is &quot;less so than it used to be.&quot; Labro&apos;s follow-up &quot;The End of PPLdump&quot; walks through how a 2021-era SymLink + KnownDlls trick that defeated PPL was patched, and how the post-patch PPL invariant holds for current Windows servicing branches [@itm4n-ppldump]. Combined with HVCI and VBS-on-by-default in newer Windows 11 builds, the modern SChannel session key is genuinely harder to lift than it was in 2019.&lt;/p&gt;

Operators frequently set `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL = 1` and stop there. Microsoft&apos;s Configure-Added-LSA-Protection doc walks through the additional values (`RunAsPPLBoot` for the boot-level enforcement, the corresponding UEFI variable for tamper resistance) that complete the posture [@ms-learn-lsa-protection]. The minimum recommended configuration is not a single value in a single hive; reading the official doc end to end is faster than rediscovering this from a bug report.
&lt;p&gt;Modern SChannel is the substrate plus the trust pipeline plus the CredSSP RDP wrapper plus the LSASS moat. The one piece still in flight as of mid-2026 is the cryptographic primitive nobody had in 2009 -- the post-quantum hybrid key exchange.&lt;/p&gt;
&lt;h2&gt;9. The Post-Quantum Pivot: ML-KEM, SymCrypt, and Hybrid TLS 1.3&lt;/h2&gt;
&lt;p&gt;On August 13, 2024, NIST published &lt;strong&gt;FIPS 203&lt;/strong&gt; -- the standard for ML-KEM, the first quantum-resistant key-encapsulation mechanism the United States government endorses for production use [@fips-203]. The standard defines three parameter sets (ML-KEM-512, ML-KEM-768, ML-KEM-1024) with security grounded in the Module Learning With Errors problem. The SymCrypt CHANGELOG entry for v103.5.0 reads, verbatim: &quot;Add ML-KEM per final FIPS 203&quot; [@symcrypt-changelog]. That single line is what the receipts on Microsoft&apos;s twenty-year algorithm-agility bet look like in the present tense.&lt;/p&gt;

A **KEM** is a public-key construction that, given a recipient&apos;s public key, produces a (ciphertext, shared-secret) pair such that the recipient can recover the shared secret from the ciphertext using its private key. ML-KEM is the NIST-standardised KEM derived from the CRYSTALS-Kyber proposal; ML-KEM-768 generates a 1184-byte public key and a 1088-byte ciphertext and produces a 32-byte shared secret. FIPS 203 [@fips-203] is the final standard; SymCrypt v103.5.0 is the first SymCrypt release shipping ML-KEM per that standard [@symcrypt-changelog].
&lt;h3&gt;What is shipping&lt;/h3&gt;
&lt;p&gt;The PQC primitives Microsoft has rolled into SymCrypt are publicly tracked in the project CHANGELOG [@symcrypt-changelog]:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;v103.5.0&lt;/strong&gt; -- ML-KEM (FIPS 203) [@fips-203].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v103.6.0&lt;/strong&gt; -- LMS (NIST SP 800-208 stateful hash-based signature) and AES-KW(P).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v103.7.0&lt;/strong&gt; -- ML-DSA (FIPS 204) [@fips-204].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v103.11.0&lt;/strong&gt; -- Composite ML-KEM (hybrid ML-KEM with a classical KEM).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v103.12.0&lt;/strong&gt; -- Composite ML-DSA (hybrid ML-DSA with a classical signature scheme).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v103.12.1&lt;/strong&gt; -- AVX-512 AES-GCM (up to ~35% throughput improvement).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CNG exposes the matching &lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt; with parameter-set selectors -- &lt;code&gt;BCRYPT_MLKEM_PARAMETER_SET_768&lt;/code&gt;, &lt;code&gt;BCRYPT_MLKEM_PARAMETER_SET_1024&lt;/code&gt;, and so on [@ms-learn-cng-mlkem-examples]. The Microsoft Learn page for the CNG ML-KEM API surface carries an explicit &quot;prerelease product / Windows Insider Preview&quot; banner. The article therefore frames SChannel&apos;s PQC support as &lt;strong&gt;preview / Insider-channel as of mid-2026&lt;/strong&gt;, with broader GA rollout in flight; the Microsoft Tech Community PQC announcement (December 2024) is the narrative anchor and the Insider-Preview banner on the API doc is the technical hedge [@ms-tech-community-pqc][@ms-tech-community-pqc-companion].&lt;/p&gt;
&lt;p&gt;The hash-based and stateless-hash-based signature side of PQC (SLH-DSA, FIPS 205 [@fips-205]) is shipping in SymCrypt and CNG along the same trajectory. Section 11 returns to why the &lt;em&gt;signature&lt;/em&gt;-side PQC transition is harder than the &lt;em&gt;KEM&lt;/em&gt;-side transition.&lt;/p&gt;
&lt;h3&gt;Hybrid TLS 1.3 key exchange: &lt;code&gt;X25519MLKEM768&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The IETF-converged named group for the most-deployed hybrid is &lt;code&gt;X25519MLKEM768&lt;/code&gt;, defined in &lt;code&gt;draft-ietf-tls-ecdhe-mlkem&lt;/code&gt; (Kris Kwiatkowski / PQShield, Panos Kampanakis / AWS, Bas Westerbaan / Cloudflare, Douglas Stebila / University of Waterloo; currently -05 as of May 26, 2026) [@draft-ietf-tls-ecdhe-mlkem]. The draft also defines &lt;code&gt;SecP256r1MLKEM768&lt;/code&gt; and &lt;code&gt;SecP384r1MLKEM1024&lt;/code&gt; for deployments that prefer NIST curves over X25519.&lt;/p&gt;
&lt;p&gt;The handshake mechanics are clean. The client sends &lt;code&gt;mlkem_pk || x25519_pk&lt;/code&gt; (1184 + 32 = 1216 bytes) in its &lt;code&gt;key_share&lt;/code&gt;; the server responds with &lt;code&gt;mlkem_ct || x25519_pk&lt;/code&gt; (1088 + 32 = 1120 bytes); both sides compute &lt;code&gt;shared_secret = mlkem_ss || x25519_ss&lt;/code&gt; (32 + 32 = 64 bytes) and feed that into TLS 1.3&apos;s HKDF-Extract as &lt;code&gt;IKM&lt;/code&gt;.&lt;/p&gt;

sequenceDiagram
    participant Client
    participant Server&lt;pre&gt;&lt;code&gt;Note over Client: Generate X25519 keypair and ML-KEM-768 keypair
Client-&amp;gt;&amp;gt;Server: ClientHello with key_share (mlkem_pk concatenated with x25519_pk -- 1216 B)
Note over Server: Generate X25519 keypair, ML-KEM encapsulate to client mlkem_pk
Server-&amp;gt;&amp;gt;Client: ServerHello with key_share (mlkem_ct concatenated with x25519_pk -- 1120 B)
Note over Client: X25519 DH, ML-KEM decapsulate
Note over Client,Server: shared_secret -- mlkem_ss concatenated with x25519_ss (64 B)
Note over Client,Server: HKDF-Extract over shared_secret continues TLS 1.3 key schedule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The construction is &lt;strong&gt;defence-in-depth against either a classical-only break or a quantum-only break&lt;/strong&gt;: an adversary must defeat &lt;em&gt;both&lt;/em&gt; X25519 and ML-KEM-768 to recover the session key, and the hybrid analysis (Bindel et al., PQCrypto 2019) shows the construction is at least as secure as the stronger of the two components. The minor cost is the inflated &lt;code&gt;ClientHello&lt;/code&gt; and &lt;code&gt;ServerHello&lt;/code&gt; (about 1.2 KB extra) and a couple of milliseconds of ML-KEM operations.&lt;/p&gt;
&lt;p&gt;{`
// Pseudocode for the X25519MLKEM768 shared-secret concatenation.
// In real SChannel: BCryptSecretAgreement(BCRYPT_ECDH_P256_ALGORITHM_HANDLE / X25519, ...)
//                 + BCryptDecapsulate(BCRYPT_MLKEM_ALG_HANDLE, parameter_set = 768, ...)&lt;/p&gt;
&lt;p&gt;function x25519_dh(privA, pubB)          { return new Uint8Array(32).fill(0xAA); }  // 32 B
function mlkem768_decaps(privA, ct)      { return new Uint8Array(32).fill(0xBB); }  // 32 B&lt;/p&gt;
&lt;p&gt;const x25519_ss = x25519_dh(&apos;clientX25519Priv&apos;, &apos;serverX25519Pub&apos;);
const mlkem_ss  = mlkem768_decaps(&apos;clientMLKEMPriv&apos;, &apos;serverMLKEMCt&apos;);&lt;/p&gt;
&lt;p&gt;const hybrid_secret = new Uint8Array(64);
hybrid_secret.set(mlkem_ss, 0);
hybrid_secret.set(x25519_ss, 32);&lt;/p&gt;
&lt;p&gt;console.log(&apos;IKM length for HKDF-Extract:&apos;, hybrid_secret.length, &apos;bytes&apos;);
console.log(&apos;First byte: 0x&apos; + hybrid_secret[0].toString(16),
            &apos;(from ML-KEM half, defends against quantum break)&apos;);
console.log(&apos;Byte 32: 0x&apos; + hybrid_secret[32].toString(16),
            &apos;(from X25519 half, defends against classical break)&apos;);
`}&lt;/p&gt;
&lt;h3&gt;The agility payoff&lt;/h3&gt;
&lt;p&gt;This rotation is the cleanest demonstration of Section 4&apos;s thesis. Adding &lt;code&gt;X25519MLKEM768&lt;/code&gt; to SChannel required: (a) a SymCrypt primitive (v103.5.0+ for ML-KEM, with X25519 long present per RFC 7748 [@rfc-7748]); (b) a new BCrypt provider registration (&lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt; and the hybrid named-group plumbing); (c) a new SChannel named-group entry. No IIS source change. No SQL Server source change. No &lt;code&gt;SslStream&lt;/code&gt; source change. Eighteen years after Vista shipped CNG, the substrate is producing receipts for a brand-new algorithm family.&lt;/p&gt;
&lt;p&gt;The deployment side is moving faster than most ten-year forecasts in cryptography ever predicted. Cloudflare&apos;s measurements (March 2024) put PQC-secured TLS 1.3 connections at &quot;nearly two percent&quot; of inbound, with the team forecasting double-digit percentages by end of 2024 [@cloudflare-pq-2024]. Cloudflare&apos;s origin-side PQC rollout has been live since September 2023 [@cloudflare-pq-origins]. Chrome / BoringSSL, Edge (via BoringSSL), and Firefox / NSS ship &lt;code&gt;X25519MLKEM768&lt;/code&gt; client-side. OpenSSL 3.5 ships ML-KEM. Server-side SChannel adoption is rolling through the Insider channel and the official Tech Community posts as of mid-2026 [@ms-learn-cng-mlkem-examples][@ms-tech-community-pqc-companion].Cloudflare&apos;s measurements (March 2024) put PQC-secured TLS 1.3 connections at &quot;nearly two percent&quot; of their inbound; by the end of 2024 they expected double-digit percentages [@cloudflare-pq-2024]. The transition is moving faster than most ten-year forecasts in cryptography ever predicted.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The hybrid PQC handshake is cheap in absolute terms but the cost is not uniform across deployment shapes. On a typical Server 2022 IIS edge with software-backed RSA-2048 plus AES-NI, sustained handshake rates run in the &lt;strong&gt;thousands per second per core&lt;/strong&gt;; the X25519MLKEM768 hybrid adds roughly &lt;strong&gt;5-10 ms of handshake latency&lt;/strong&gt;, which is in the noise relative to the per-handshake cost of an RSA-2048 signature. On a TPM-key-bound edge the picture inverts: the Microsoft Platform Crypto Provider is serialised by TPM 2.0 &lt;code&gt;TPM2_Sign&lt;/code&gt; latency (tens of milliseconds per signature), so sustained handshake rates sit in the &lt;strong&gt;tens to roughly one hundred handshakes per second per host&lt;/strong&gt;, and the same ~5-10 ms hybrid delta becomes a non-trivial fraction of the per-handshake budget. AES-NI bulk throughput on AES-256-GCM is roughly &lt;strong&gt;5-10 Gbps per core&lt;/strong&gt; (the AVX-512 AES-GCM landing in SymCrypt v103.12.1 shifts that ceiling further [@symcrypt-changelog]) so the post-handshake data path is not the bottleneck. Operator decision support: if you are software-key-bound, the hybrid PQC delta is noise. If you are TPM-key-bound, your handshake rate is already in the tens, and the hybrid delta is meaningful enough to budget for.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; As of article publication (mid-2026), SChannel&apos;s &lt;code&gt;X25519MLKEM768&lt;/code&gt; support is preview / Insider-channel; the CNG ML-KEM page carries the explicit Windows Insider Preview banner [@ms-learn-cng-mlkem-examples]. Track the SymCrypt CHANGELOG for primitive landings [@symcrypt-changelog] and the Microsoft Tech Community PQC posts for OS-channel GA announcements [@ms-tech-community-pqc]. Do not assert GA dates that have not landed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the PQC rotation is the agility payoff, the next question is the obvious one: how does SChannel&apos;s answer compare to the other TLS stacks shipping on the same calendar? OpenSSL, BoringSSL, NSS, and Apple&apos;s Network framework have all had to solve the same algorithm-agility problem -- and they have all made different trade-offs.&lt;/p&gt;
&lt;h2&gt;10. Competing Approaches: How Other TLS Stacks Solve Algorithm Agility&lt;/h2&gt;
&lt;p&gt;Algorithm agility is not a property of TLS-the-protocol. It is a property of the &lt;em&gt;substrate&lt;/em&gt; underneath the protocol. Every major TLS implementation has had to answer the same question -- &quot;how do we add a new primitive without breaking our consumers?&quot; -- and the answers are surprisingly different.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Substrate model&lt;/th&gt;
&lt;th&gt;Stability commitment&lt;/th&gt;
&lt;th&gt;PQC integration as of mid-2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SChannel / CNG&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BCrypt providers + NCrypt KSPs; Win32 API-stable [@ms-learn-cng-portal]&lt;/td&gt;
&lt;td&gt;Strong: Win32 SSPI surface frozen&lt;/td&gt;
&lt;td&gt;ML-KEM in SymCrypt v103.5.0 [@symcrypt-changelog]; &lt;code&gt;X25519MLKEM768&lt;/code&gt; Insider Preview [@ms-learn-cng-mlkem-examples]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenSSL 3.x&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OSSL_PROVIDER&lt;/code&gt; modules via &lt;code&gt;OSSL_DISPATCH&lt;/code&gt; arrays [@openssl-provider7-3.0]&lt;/td&gt;
&lt;td&gt;Strong-by-major-version&lt;/td&gt;
&lt;td&gt;OQS-Provider for early PQC; ML-KEM in OpenSSL 3.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BoringSSL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single source tree; &quot;rolling release&quot;; no provider model [@boringssl-readme]&lt;/td&gt;
&lt;td&gt;Explicitly none (&quot;no guarantees of API or ABI stability&quot;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X25519MLKEM768&lt;/code&gt; shipping; consumer vendoring required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NSS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OASIS PKCS #11 v3.1 modules via &lt;code&gt;CK_FUNCTION_LIST&lt;/code&gt; [@nss-3.111-release-notes][@oasis-pkcs11-v3.1]&lt;/td&gt;
&lt;td&gt;Strong (Firefox compatibility)&lt;/td&gt;
&lt;td&gt;ML-KEM via PKCS #11 v3.1 &lt;code&gt;C_Encapsulate&lt;/code&gt; / &lt;code&gt;C_Decapsulate&lt;/code&gt;; &lt;code&gt;X25519MLKEM768&lt;/code&gt; in Firefox 132&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apple Network framework / Secure Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Framework-version pinning per OS release [@apple-network-framework][@apple-secure-transport]&lt;/td&gt;
&lt;td&gt;Strong per OS version&lt;/td&gt;
&lt;td&gt;Hybrid KEM shipping in newer Network framework releases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.NET &lt;code&gt;SslStream&lt;/code&gt; cross-platform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Delegates to host OS stack [@dotnet-cross-platform-crypto]&lt;/td&gt;
&lt;td&gt;Strong per .NET version&lt;/td&gt;
&lt;td&gt;Inherits underlying stack&apos;s PQC support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;OpenSSL 3.x: &lt;code&gt;OSSL_PROVIDER&lt;/code&gt;, explicit contexts, three in-tree providers&lt;/h3&gt;
&lt;p&gt;OpenSSL 3.0 replaced the older &lt;code&gt;ENGINE&lt;/code&gt; model with the &lt;strong&gt;&lt;code&gt;OSSL_PROVIDER&lt;/code&gt;&lt;/strong&gt; system, described in the &lt;code&gt;provider(7)&lt;/code&gt; manpage as &quot;a unit of code that provides one or more implementations for various operations for diverse algorithms&quot; [@openssl-provider7-3.0]. A provider exposes its operations through an &lt;code&gt;OSSL_DISPATCH&lt;/code&gt; array of &lt;code&gt;{function-id, function-pointer}&lt;/code&gt; pairs. The loader&apos;s entry point is a single exported function with this exact signature [@openssl-provider7-3.0]:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int OSSL_provider_init(const OSSL_CORE_HANDLE *handle,
                       const OSSL_DISPATCH *in,
                       const OSSL_DISPATCH **out,
                       void **provctx);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;in&lt;/code&gt; array gives the provider the callbacks the OpenSSL library is willing to provide to the provider (logging, error reporting, library-context queries, parameter accessors) [@openssl-provider-base7-3.0]; the &lt;code&gt;out&lt;/code&gt; array is filled in by the provider with the operations it implements. The &lt;code&gt;provctx&lt;/code&gt; is the provider&apos;s own per-instance state.&lt;/p&gt;
&lt;p&gt;OpenSSL 3.0 ships three in-tree providers: &lt;strong&gt;default&lt;/strong&gt; (the modern algorithm set), &lt;strong&gt;legacy&lt;/strong&gt; (RC4, MD4, IDEA, and other backward-compatibility primitives), and &lt;strong&gt;FIPS&lt;/strong&gt; (the FIPS 140-3-validated subset). Out-of-tree, the OQS-Provider plugs PQC primitives into OpenSSL without recompiling the OpenSSL build itself. The substantive contrast with CNG: OpenSSL makes the provider context an &lt;em&gt;explicit&lt;/em&gt; parameter via &lt;code&gt;OSSL_LIB_CTX *&lt;/code&gt;, which means multiple isolated provider sets can coexist inside one process (a FIPS-validated workload and a legacy workload in the same binary). CNG keeps provider dispatch global per-process. Both models are functionally agile; OpenSSL&apos;s is more &lt;em&gt;compositional&lt;/em&gt; at runtime, while CNG&apos;s is more &lt;em&gt;governed&lt;/em&gt; through the Windows servicing branch.&lt;/p&gt;
&lt;h3&gt;BoringSSL&apos;s anti-agility position&lt;/h3&gt;
&lt;p&gt;BoringSSL is Google&apos;s TLS stack used by Chromium and (via Chromium) Microsoft Edge. The project README says, verbatim:&lt;/p&gt;

Although BoringSSL is an open source project, it is not intended for general use, as OpenSSL is. We don&apos;t recommend that third parties depend upon it. Doing so is likely to be frustrating because there are no guarantees of API or ABI stability. -- BoringSSL README [@boringssl-readme]
&lt;p&gt;BoringSSL achieves agility by &lt;em&gt;refusing&lt;/em&gt; to absorb it as a public API. Consumers vendor BoringSSL into their own build tree and accept the lift of tracking head. Chromium does this; Edge inherits it; cURL ships configurations that link against BoringSSL when the consumer asks for it. The model is the inverse of CNG&apos;s: maximum velocity for the maintainer, maximum churn for the consumer. For a vendor whose chief constraint is API stability for the Win32 / .NET universe, BoringSSL&apos;s model is structurally incompatible. For a vendor whose chief constraint is shipping the modern internet&apos;s TLS posture into a browser monthly, BoringSSL&apos;s model is the right answer.&lt;/p&gt;
&lt;h3&gt;NSS and PKCS #11&lt;/h3&gt;
&lt;p&gt;Mozilla&apos;s NSS predates almost every other stack here and uses the &lt;strong&gt;OASIS PKCS #11&lt;/strong&gt; (Cryptoki) module standard as its agility hinge [@oasis-pkcs11-v3.1]. A PKCS #11 module exposes a single entry point, &lt;code&gt;C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList)&lt;/code&gt;, which returns a table of roughly seventy function pointers. Those functions are organised around a three-level hierarchy: a &lt;em&gt;slot&lt;/em&gt; is a place where a &lt;em&gt;token&lt;/em&gt; sits; a &lt;em&gt;token&lt;/em&gt; holds &lt;em&gt;objects&lt;/em&gt; (keys, certificates, data); cryptographic operations are invoked against an object referenced by &lt;code&gt;CK_OBJECT_HANDLE&lt;/code&gt; and parametrised by a &lt;code&gt;CK_MECHANISM&lt;/code&gt; (e.g. &lt;code&gt;CKM_AES_GCM&lt;/code&gt;, &lt;code&gt;CKM_ECDH1_DERIVE&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;NSS itself ships two PKCS #11 modules out of the box: the &lt;strong&gt;NSS softoken&lt;/strong&gt; (&lt;code&gt;softokn3&lt;/code&gt;, in-process software primitives) and the &lt;strong&gt;NSS FIPS softoken&lt;/strong&gt; (the FIPS-validated variant). Hardware PKCS #11 modules for HSMs and smart cards load through the same &lt;code&gt;SECMOD_LoadUserModule&lt;/code&gt; API. PQC arrived in NSS via PKCS #11 v3.1&apos;s KEM operations: &lt;code&gt;C_Encapsulate&lt;/code&gt; and &lt;code&gt;C_Decapsulate&lt;/code&gt; are standardised verbs that ML-KEM-768 implementations can expose without needing the historic &lt;code&gt;CKM_VENDOR_DEFINED&lt;/code&gt; mechanism-ID reservation pattern. NSS 3.111 (released April 28, 2025) is the release marker for full PKCS #11 v3.1 adoption in NSS [@nss-3.111-release-notes]; Firefox shipped &lt;code&gt;X25519MLKEM768&lt;/code&gt; client-side in Firefox 132 (October 2024). The high-order contrast with CNG: PKCS #11 is a &lt;em&gt;cross-vendor industry standard&lt;/em&gt;, so the same NSS / Firefox runtime can talk to a Mozilla softoken, an HSM module, and a smart-card module through a single interface; CNG is single-vendor (Microsoft) but exposes a fully Microsoft-curated provider universe through a stable Win32 API.&lt;/p&gt;
&lt;h3&gt;Apple Network framework and CryptoKit&lt;/h3&gt;
&lt;p&gt;Apple&apos;s TLS-stack history splits into the deprecated &lt;strong&gt;Secure Transport&lt;/strong&gt; API [@apple-secure-transport] and the modern &lt;strong&gt;Network framework&lt;/strong&gt; introduced in macOS 10.14 and iOS 12 [@apple-network-framework]. Secure Transport&apos;s design was C-API and typed-enum: &lt;code&gt;SSLProtocol&lt;/code&gt; selected the TLS version; &lt;code&gt;SSLCipherSuite&lt;/code&gt; integers were the IANA cipher-suite codepoints; the developer worked with &lt;code&gt;SSLContextRef&lt;/code&gt; handles much as a Windows developer works with &lt;code&gt;CtxtHandle&lt;/code&gt;. The agility model was &lt;em&gt;named-enum-per-OS-release&lt;/em&gt;: every TLS version and cipher was a compile-time constant, and the SDK version the application was built against determined what was selectable.&lt;/p&gt;
&lt;p&gt;Network framework moved the API to a Swift-first surface (&lt;code&gt;NWProtocolTLS.Options&lt;/code&gt;, &lt;code&gt;sec_protocol_options_set_min_tls_protocol_version&lt;/code&gt;) and started Apple&apos;s deprecation glide for Secure Transport. On top of the network-layer primitives, &lt;strong&gt;CryptoKit&lt;/strong&gt; (iOS 13 / macOS 10.15) provides the Swift-idiomatic high-level crypto API for symmetric AEAD, ECDH, ECDSA, and (via subsequent OS releases) the post-quantum primitives [@apple-cryptokit]. The cadence is bound to Apple&apos;s annual OS release: a new algorithm becomes available when the OS that ships it becomes available, and applications that need it bump their minimum-deployment target.&lt;/p&gt;
&lt;p&gt;The structural contrast with CNG: Apple&apos;s model gives the platform vendor very tight control over what is selectable and a clean deprecation path (you simply drop a constant from a future SDK), but the cost is that &lt;em&gt;the application&apos;s algorithm options track the OS version the application is built for&lt;/em&gt;. CNG decouples those -- a Windows 11 application built against a 2015 Win32 SDK still sees new BCrypt algorithm strings as the OS ships them, because the dispatch is by string at runtime.&lt;/p&gt;
&lt;h3&gt;.NET &lt;code&gt;SslStream&lt;/code&gt; -- one API, three host backends&lt;/h3&gt;
&lt;p&gt;.NET&apos;s &lt;code&gt;System.Net.Security.SslStream&lt;/code&gt; is &lt;em&gt;identical&lt;/em&gt; on every host. The implementation, however, delegates to the host operating system&apos;s TLS stack. On Windows it calls into SChannel through SSPI; on Linux it calls into OpenSSL via &lt;code&gt;System.Security.Cryptography.Native.OpenSsl&lt;/code&gt;; on macOS it calls into Apple&apos;s Network framework via &lt;code&gt;System.Security.Cryptography.Native.Apple&lt;/code&gt; [@dotnet-cross-platform-crypto]. There is no &quot;pick a backend&quot; knob in &lt;code&gt;SslStream&lt;/code&gt;; the runtime picks whichever backend the host OS provides.&lt;/p&gt;
&lt;p&gt;The agility consequence for PQC is direct. A &lt;code&gt;.NET 10&lt;/code&gt; application running on a Windows Insider build whose SChannel has &lt;code&gt;X25519MLKEM768&lt;/code&gt; enabled by default will negotiate hybrid PQC automatically. The same application running on macOS gets classical X25519 until Apple ships hybrid in Network framework. The same application running on Linux against OpenSSL 3.5 gets ML-KEM via OpenSSL&apos;s in-tree implementation. &lt;strong&gt;The application source code never changes; the wire-level cryptography is whatever the host&apos;s TLS stack negotiates.&lt;/strong&gt; This is the agility property in cross-platform clothing -- and it works because each host&apos;s substrate is itself agile.&lt;/p&gt;
&lt;h3&gt;Five substrates, five answers&lt;/h3&gt;
&lt;p&gt;The cross-stack comparison surfaces the meta-point. Five substrates: CNG, OpenSSL &lt;code&gt;OSSL_PROVIDER&lt;/code&gt;, BoringSSL&apos;s vendored tree, PKCS #11, Apple&apos;s named-enum SDK. Five answers, all functional, all optimising for different deployment models. SChannel / CNG is the most &lt;em&gt;registry-driven and single-vendor-extensible&lt;/em&gt;; OpenSSL is the most &lt;em&gt;context-explicit&lt;/em&gt;; PKCS #11 is the most &lt;em&gt;cross-vendor-standardised&lt;/em&gt;; BoringSSL is the most &lt;em&gt;aggressive-by-refusing-stability&lt;/em&gt;; Apple is the most &lt;em&gt;named-enum-SDK-bound&lt;/em&gt;. None of these is &quot;the right&quot; answer -- each is the answer that fits its vendor&apos;s deployment shape. The agility property is &lt;em&gt;of the substrate&lt;/em&gt;, and the right substrate depends on what you ship and to whom.&lt;/p&gt;
&lt;p&gt;Agility is the &lt;em&gt;capacity&lt;/em&gt; for rotation. Whether the rotation actually happens is a separate problem -- one that the empirical evidence of TLS 1.0 / 1.1&apos;s 25-year tail tells a sobering story about.&lt;/p&gt;
&lt;h2&gt;11. Limits and Open Problems&lt;/h2&gt;
&lt;p&gt;Algorithm agility is necessary. It is not sufficient. TLS 1.0 was published in 1999 [@rfc-2246]; default-off in stable Windows did not arrive until 2024-2025 [@ms-learn-tls-registry-settings]. Twenty-five years. The substrate could have rotated TLS 1.0 out a decade earlier; the world would not move.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Agility lets the substrate add a new primitive and lets operators disable an old one. It cannot force operators of TLS-1.2-only or TLS-1.0-only endpoints to upgrade. The substrate solved the rotation problem; the world is the bottleneck. Trust-store distrust events, OS-level deprecation defaults, browser warnings, and eventual code-path removal are the levers that close the gap -- but each operates on the scale of years, not weeks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This re-organises the reader&apos;s understanding of why Microsoft&apos;s posture on PQC is &quot;ship and hedge&quot; rather than &quot;ship and declare victory.&quot; The substrate is ahead of the protocol; the protocol is ahead of the deployments; the deployments are years behind.&lt;/p&gt;
&lt;h3&gt;The downgrade-attack envelope&lt;/h3&gt;
&lt;p&gt;RFC 7568 (June 2015) formally prohibits SSL 3.0 [@rfc-7568]; RFC 6176 (March 2011) formally prohibits SSL 2.0 [@rfc-6176]. The TLS Fallback SCSV cipher suite (RFC 7507) bounded downgrade attacks within TLS 1.0 / 1.1 / 1.2. TLS 1.3&apos;s &lt;code&gt;ServerHello.random&lt;/code&gt; downgrade-resistance sentinel (RFC 8446 §4.1.3 [@rfc-8446]) closes the downgrade attack surface &lt;em&gt;within&lt;/em&gt; TLS 1.3. The remaining downgrade exposure lives at the boundary with TLS-1.2-only counterparties -- a boundary that shrinks every year but does not yet close.&lt;/p&gt;
&lt;h3&gt;Signature-side PQC and the chain-size problem&lt;/h3&gt;
&lt;p&gt;The PQC hybrid KEM transition is the easy half of the post-quantum migration. The signature side is harder. ML-DSA-65 produces ~3.3 KB signatures with ~2 KB public keys [@fips-204]; SLH-DSA at 128-bit security produces signatures in the 7 to 17 KB range [@fips-205]; Falcon (FN-DSA in the NIST nomenclature) produces ~1 KB signatures but is harder to implement correctly because of its floating-point Gaussian sampling.&lt;/p&gt;
&lt;p&gt;Why this matters for SChannel: TLS server certificate chains are sent in the &lt;code&gt;Certificate&lt;/code&gt; handshake message. A chain that fits inside TCP&apos;s initial congestion window (typically 10 segments, or about 14.6 KB) ships in one round trip; a chain that overflows the IW takes another RTT. Adding a 3.3 KB ML-DSA signature plus a 2 KB ML-DSA public key to every cert in a chain rapidly blows past 14.6 KB for a typical leaf-intermediate-root structure. The community working hypothesis is that the hybrid-signature transition in TLS will lag the hybrid-KEM transition by years; SymCrypt&apos;s Composite ML-DSA support (v103.12.0) [@symcrypt-changelog] is the substrate-side preparation for that transition, but the IETF TLS WG signature-side drafts are still in flight.&lt;/p&gt;
&lt;h3&gt;Composite-identifier namespace sprawl in CNG&lt;/h3&gt;
&lt;p&gt;Every hybrid construction adds at least one new CNG algorithm identifier. &lt;code&gt;X25519MLKEM768&lt;/code&gt;, &lt;code&gt;SecP256r1MLKEM768&lt;/code&gt;, &lt;code&gt;SecP384r1MLKEM1024&lt;/code&gt; already exist. Composite ML-DSA + ECDSA is in flight. If pure ML-KEM-1024 and pure SLH-DSA are eventually default-on, the algorithm namespace doubles per hybrid family. The substrate is &lt;em&gt;capable&lt;/em&gt; of absorbing the sprawl; whether the cipher-suite registry remains legible to administrators is a separate user-interface problem.&lt;/p&gt;
&lt;h3&gt;The opaque-engine bargain&lt;/h3&gt;
&lt;p&gt;SymCrypt is open since July 2019 and externally auditable [@symcrypt-github]. The SChannel SSP binary itself remains closed-source. External behavioural verification -- Hubert Kario&apos;s &lt;code&gt;tlsfuzzer&lt;/code&gt; -- is the closest the public has to a formal specification of &lt;code&gt;schannel.dll&lt;/code&gt;&apos;s wire-level behaviour [@tlsfuzzer-github]. The project&apos;s framing is precise: it &quot;doesn&apos;t check only that the system under test didn&apos;t crash, it checks that it returned correct error messages&quot; [@tlsfuzzer-github]. That is the closest practitioners get to a behavioural spec without source.&lt;/p&gt;
&lt;p&gt;The asymmetry has a name in the article&apos;s argument: open-source substrate, closed-source SSP. The agility receipts of Sections 5 and 9 are auditable at the primitive layer. The parsing-path correctness of Section 6 is not -- the coordinated-disclosure intake of MS14-066 (with IBM X-Force researcher Robert Freeman credited as the discoverer per the bulletin&apos;s acknowledgments [@ms14-066]) is the kind of receipt the binary-only delivery model can produce. Modern external fuzzing has narrowed the gap but does not close it.&lt;/p&gt;
&lt;h3&gt;Legacy protocol &lt;em&gt;removal&lt;/em&gt; versus disablement&lt;/h3&gt;
&lt;p&gt;Disablement by default is universal in Windows 11 / Server 2022+. &lt;em&gt;Removing the negotiation code paths&lt;/em&gt; is a separate, slower trajectory. SSL 3.0&apos;s code paths are largely gone from current SChannel; TLS 1.0 / 1.1 code paths remain reachable behind registry flags because some long-tail enterprise scenarios still require them. The protocol surface of SChannel is wider than its default-enabled surface; an audit posture must account for the difference.&lt;/p&gt;
&lt;h3&gt;Dead ends and the diseases they failed to cure&lt;/h3&gt;
&lt;p&gt;The five agility receipts of Section 5 are the &lt;em&gt;primitive-rotation&lt;/em&gt; story. But not every TLS failure is a primitive failure, and the substrate could not save Windows from the four most-instructive engineering dead ends the IETF eventually had to legislate out of the protocol itself. &lt;strong&gt;CRIME / TLS-level DEFLATE compression&lt;/strong&gt; (Rizzo and Duong, ekoparty 2012) was a compression-then-encryption side-channel that no primitive substitution could fix; TLS 1.3 removed compression from the protocol entirely (RFC 8446 §4.1.2 [@rfc-8446]) and SChannel never shipped TLS-level compression in the first place. &lt;strong&gt;Insecure renegotiation (CVE-2009-3555)&lt;/strong&gt; -- Ray and Dispensa, November 2009 -- let an MITM splice attacker-prefix application data into a victim&apos;s authenticated session because the mid-session re-handshake had no transcript binding to the prior session; RFC 5746 (Eric Rescorla et al., February 2010 [@rfc-5746]) added the &lt;code&gt;renegotiation_info&lt;/code&gt; extension, Microsoft shipped the fix in SChannel via KB977377 / KB980436 in early 2010, and TLS 1.3 removed renegotiation outright in favour of the narrowly-scoped &lt;code&gt;KeyUpdate&lt;/code&gt; message and post-handshake &lt;code&gt;CertificateRequest&lt;/code&gt;. &lt;strong&gt;Anonymous Diffie-Hellman cipher suites&lt;/strong&gt; (TLS 1.0 through 1.2 specified &lt;code&gt;TLS_DH_anon_*&lt;/code&gt; and &lt;code&gt;TLS_ECDH_anon_*&lt;/code&gt; with no server certificate at all -- forward secrecy without authentication) were off-by-default in every major stack including SChannel and removed from the TLS 1.3 cipher-suite namespace entirely. &lt;strong&gt;Export-grade RSA / FREAK&lt;/strong&gt; (Beurdouche, Bhargavan and colleagues, IEEE S&amp;amp;P 2015 [@smacktls]) used roughly $100 of EC2 compute to break 512-bit RSA in hours and force-downgrade non-export-aware servers; Microsoft pruned the export-RSA suites from SChannel&apos;s default set via MS15-031 / KB3046049 in March 2015 [@smacktls].&lt;/p&gt;
&lt;p&gt;These four dead ends share a structural lesson: each is a &lt;em&gt;different axis of failure&lt;/em&gt;. CRIME was a side-channel no algorithm could fix. Insecure renegotiation was a feature whose protocol design admitted MITM splicing. Anonymous DH was a configuration the protocol should never have exposed. FREAK was an obsolete primitive whose continued availability invited downgrade. All four sit &lt;em&gt;above&lt;/em&gt; the substrate -- none is a primitive-design defect like MD5 or DES-56. The thesis the article advances -- that the substrate changed because it &lt;em&gt;had to support&lt;/em&gt; the changes but did not &lt;em&gt;invent&lt;/em&gt; them -- is illustrated negatively by these four: the &lt;em&gt;protocol&lt;/em&gt; axis had to do the work, often by removal rather than refinement. The agility receipt of Section 5 G5 (TLS 1.0 / 1.1 disablement) is, in this light, just the most visible item in a longer ledger.&lt;/p&gt;
&lt;p&gt;If the theoretical limits are humbling, the practical day-to-day -- &quot;what should I actually do with my SChannel-served TLS endpoints this Monday morning?&quot; -- has a much cleaner set of answers.&lt;/p&gt;
&lt;h2&gt;12. Practical Guide: Nine Things to Do on a Windows-Served TLS Endpoint&lt;/h2&gt;
&lt;p&gt;A working operator&apos;s reference distilled to the essentials -- the nine things that, if you do nothing else this quarter, materially improve the security posture of a Windows-served TLS endpoint.&lt;/p&gt;
&lt;h3&gt;1. Inventory&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;# Cipher suites enabled on this host, in negotiation order
Get-TlsCipherSuite | Select-Object -Property Name, Cipher, CipherLength, KeyType, Exchange

# ECC named groups (TLS 1.3 key shares; X25519, secp256r1, secp384r1; PQC hybrids on newer builds)
Get-TlsEccCurve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pair this with a registry walk of &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\&lt;/code&gt; -- &lt;code&gt;Protocols\&amp;lt;ver&amp;gt;\&amp;lt;role&amp;gt;\Enabled&lt;/code&gt; and &lt;code&gt;DisabledByDefault&lt;/code&gt; for protocol versions, &lt;code&gt;Ciphers\&amp;lt;algorithm&amp;gt;\Enabled&lt;/code&gt; for primitive disables, and &lt;code&gt;Hashes\&amp;lt;algorithm&amp;gt;\Enabled&lt;/code&gt; for handshake-hash disables [@ms-learn-tls-registry-settings].&lt;/p&gt;
&lt;h3&gt;2. Disable the legacy protocol versions&lt;/h3&gt;
&lt;p&gt;Set &lt;code&gt;SCHANNEL\Protocols\SSL 3.0\&amp;lt;role&amp;gt;\Enabled = 0&lt;/code&gt; and &lt;code&gt;DisabledByDefault = 1&lt;/code&gt; for both &lt;code&gt;Client&lt;/code&gt; and &lt;code&gt;Server&lt;/code&gt; sub-keys. Repeat for TLS 1.0 and TLS 1.1. The asymmetry between &lt;code&gt;Client&lt;/code&gt; and &lt;code&gt;Server&lt;/code&gt; hives bites: an outbound &lt;code&gt;WinHTTP&lt;/code&gt; call from your IIS worker is governed by the &lt;code&gt;Client&lt;/code&gt; sub-key even though the server itself is gated by &lt;code&gt;Server&lt;/code&gt; [@ms-learn-tls-registry-settings].&lt;/p&gt;
&lt;h3&gt;3. Disable RC4 and 3DES at the cipher level&lt;/h3&gt;
&lt;p&gt;RC4: KB2868725 [@ms-advisory-2868725] introduced the mechanism. Set &lt;code&gt;Ciphers\RC4 40/128\Enabled = 0&lt;/code&gt;, &lt;code&gt;Ciphers\RC4 56/128\Enabled = 0&lt;/code&gt;, &lt;code&gt;Ciphers\RC4 64/128\Enabled = 0&lt;/code&gt;, &lt;code&gt;Ciphers\RC4 128/128\Enabled = 0&lt;/code&gt;. 3DES: &lt;code&gt;Ciphers\Triple DES 168\Enabled = 0&lt;/code&gt;. Then verify with &lt;code&gt;Get-TlsCipherSuite&lt;/code&gt; that no &lt;code&gt;*RC4*&lt;/code&gt; or &lt;code&gt;*3DES*&lt;/code&gt; suites are still listed.&lt;/p&gt;
&lt;h3&gt;4. Cipher-suite ordering for TLS 1.2&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;SSL Cipher Suite Order&lt;/code&gt; GPO is the lever. Put &lt;code&gt;ECDHE&lt;/code&gt; + &lt;code&gt;AES-GCM&lt;/code&gt; suites at the top; keep CHACHA20-POLY1305 as a fallback for clients without AES-NI; pull legacy AES-CBC suites to the bottom. The Microsoft Learn &quot;Manage TLS&quot; page walks through the GPO interaction [@ms-learn-manage-tls].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Setting an explicit cipher-suite order via the older &lt;code&gt;SSL Cipher Suite Order&lt;/code&gt; GPO can accidentally exclude TLS 1.3 cipher suites if the list does not enumerate them. The TLS 1.3 suites (&lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt;, &lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt;, &lt;code&gt;TLS_CHACHA20_POLY1305_SHA256&lt;/code&gt;) must appear in the configured list, otherwise TLS 1.3 effectively gets disabled on the host. Verify with &lt;code&gt;Get-TlsCipherSuite&lt;/code&gt; after applying any GPO change.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;5. Enable OCSP stapling on IIS, and enable CAPI2/Operational logging for distrust observability&lt;/h3&gt;
&lt;p&gt;OCSP stapling is on by default in modern IIS. Verify that your front door is sending stapled responses (via &lt;code&gt;openssl s_client -status -connect host:443 &amp;lt; /dev/null | grep -i ocsp&lt;/code&gt; from a test client). If your CA does not support OCSP for the issued cert, the stapling fails silently and you lose the revocation channel; pick a CA that does.&lt;/p&gt;
&lt;p&gt;For trust-store observability, enable the per-host CAPI2/Operational tracing channel with &lt;code&gt;wevtutil sl Microsoft-Windows-CAPI2/Operational /e:true&lt;/code&gt; and watch for the chain-engine events the Microsoft Learn FCPCA-removal article enumerates: Event ID 11 (chain-build failures), Event ID 30 (SSL or NTAuth policy failures), Event ID 90 (every certificate consulted during chain build) [@ms-learn-fcpca-removal]. The FCPCA article also documents the empirical &quot;one to seven days&quot; propagation latency between an MTRP distrust landing in &lt;code&gt;authrootstl.cab&lt;/code&gt; and a given client actually applying it -- the same window applies to any future CCADB-coordinated removal (cross-reference Section 7).&lt;/p&gt;
&lt;h3&gt;6. Enforce RunAsPPL &lt;strong&gt;and&lt;/strong&gt; Credential Guard&lt;/h3&gt;
&lt;p&gt;These are &lt;em&gt;complementary&lt;/em&gt;, not alternatives [@itm4n-runasppl]. Set &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL = 1&lt;/code&gt; and reboot; verify LSASS comes back as a Protected Process with &lt;code&gt;Get-Process lsass | Select Name, Protect*&lt;/code&gt; [@ms-learn-lsa-protection]. Then enable Credential Guard via Group Policy or MDM; on most newer Windows 11 builds it is on by default [@ms-learn-credential-guard]. Auditing-only mode (&lt;code&gt;AuditLevel&lt;/code&gt;) is the right step before enforcement to identify any legacy LSA plug-ins that fail to load as PPL.&lt;/p&gt;
&lt;h3&gt;7. Lock down CredSSP / RDP NLA on Remote Desktop Session Hosts&lt;/h3&gt;
&lt;p&gt;Confirm Network Level Authentication is enabled on any RDP Session Host (it has been default-on since Windows Server 2012 R2) [@ms-archive-nla]. Confirm the host is running CredSSP version 5 or higher, so the channel-binding hash mechanism that replaced the broken pre-CVE-2018-0886 &quot;encrypt the public key + 1&quot; scheme is in force [@ms-cssp-sequencing]. For any administrative jump-host scenario where the destination&apos;s plaintext-credential exposure must be zero, use &lt;strong&gt;Remote Credential Guard&lt;/strong&gt; (&lt;code&gt;TSRemoteGuardCreds&lt;/code&gt;) -- the destination receives only a service ticket usable for the session, not a reusable password or hash. Pair NLA enforcement with the certificate-validation knobs in item 5: the SChannel server certificate the CredSSP TLS handshake validates is the same one a TLS-only audit covers, so the trust pipeline reuse is exact.&lt;/p&gt;
&lt;h3&gt;8. FIPS-mode toggle: what &lt;code&gt;FipsAlgorithmPolicy = 1&lt;/code&gt; actually means in 2026&lt;/h3&gt;
&lt;p&gt;The Local Security Policy setting &quot;&lt;strong&gt;System cryptography: Use FIPS compliant algorithms for encryption, hashing, and signing&lt;/strong&gt;&quot; (registry: &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy\Enabled = 1&lt;/code&gt;) is the operator-side policy lever that pins SChannel, EFS, BitLocker, and RDP encryption to the FIPS 140-validated subset of CNG&apos;s catalog [@ms-learn-fips-policy]. The &quot;what it disables&quot; question has changed since the legacy &quot;TLS_RSA_WITH_3DES_EDE_CBC_SHA only&quot; framing on the policy reference page itself [@ms-learn-fips-policy]. The modern Microsoft Learn &quot;TLS Cipher Suites in Windows 11&quot; page is explicit that &quot;FIPS-compliance has become more complex with the addition of elliptic curves making the FIPS mode enabled column in previous versions of this table misleading,&quot; and points readers to NIST SP 800-52 Rev. 2 section 3.3.1 for the authoritative FIPS-approved TLS 1.2 / 1.3 cipher-suite list [@ms-learn-tls-cipher-suites-windows-11][@nist-sp-800-52r2].&lt;/p&gt;
&lt;p&gt;In practice on a Windows 11 / Server 2022 box with &lt;code&gt;FipsAlgorithmPolicy = 1&lt;/code&gt;: SChannel will negotiate TLS 1.3&apos;s &lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt; and &lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt; (the third TLS 1.3 mandatory suite, &lt;code&gt;TLS_CHACHA20_POLY1305_SHA256&lt;/code&gt;, is &lt;strong&gt;not&lt;/strong&gt; FIPS-approved because ChaCha20-Poly1305 is not on the FIPS algorithm list); for TLS 1.2 it will negotiate the ECDHE-with-AES-GCM and ECDHE-with-AES-CBC-SHA2 variants over the NIST curves P-256, P-384, and P-521 only; the X25519 named group is &lt;strong&gt;not&lt;/strong&gt; FIPS-approved as of the May 2026 Windows servicing snapshot; and the X25519MLKEM768 hybrid in Insider channels is &lt;strong&gt;not&lt;/strong&gt; FIPS-approved either, because of the X25519 component.&lt;/p&gt;
&lt;p&gt;Two-sided framing: &lt;strong&gt;SymCrypt&apos;s FIPS 140-3 validation is the &lt;em&gt;engine-side receipt&lt;/em&gt;; &lt;code&gt;FipsAlgorithmPolicy = 1&lt;/code&gt; is the &lt;em&gt;consumer-side policy lever&lt;/em&gt; that pins consumers to the validated subset.&lt;/strong&gt; Both are required for the system to be &quot;operating in FIPS mode&quot; in the CMVP sense [@ms-learn-fips-140-validation]. At the BCrypt layer, FIPS enforcement is &lt;em&gt;opt-in&lt;/em&gt; via a CNG flag that callers pass to &lt;code&gt;BCryptOpenAlgorithmProvider&lt;/code&gt;; SChannel honours the system policy directly, but legacy applications loading deprecated CryptoAPI 1.0 CSPs (&lt;code&gt;PROV_RSA_FULL&lt;/code&gt;, &lt;code&gt;rsaenh.dll&lt;/code&gt;, etc.) bypass the toggle entirely [@ms-learn-cryptographic-provider-types].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Enabling &lt;code&gt;FipsAlgorithmPolicy = 1&lt;/code&gt; is prospective only. It affects &lt;em&gt;future&lt;/em&gt; BCrypt opens, &lt;em&gt;future&lt;/em&gt; SChannel handshakes, and &lt;em&gt;future&lt;/em&gt; EFS encryptions. It does &lt;strong&gt;not&lt;/strong&gt; re-derive existing TLS session keys, does &lt;strong&gt;not&lt;/strong&gt; re-encrypt existing EFS-protected files, and may break RDP between a FIPS-on Server 2022 host and a not-FIPS-configured Windows 10 1809 client because the two ends can no longer agree on a common cipher suite. Plan rollout carefully and verify mixed-version paths before flipping the bit fleet-wide.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;9. Pilot the PQC hybrid where you can&lt;/h3&gt;
&lt;p&gt;Where Windows builds support &lt;code&gt;X25519MLKEM768&lt;/code&gt; -- presently Insider Preview channels per the CNG ML-KEM page&apos;s banner [@ms-learn-cng-mlkem-examples] -- pilot the hybrid against an internal client. Validate via Wireshark (looking for the &lt;code&gt;X25519MLKEM768&lt;/code&gt; named-group selector in &lt;code&gt;ClientHello&lt;/code&gt; / &lt;code&gt;ServerHello&lt;/code&gt; &lt;code&gt;key_share&lt;/code&gt; extensions) and a &lt;code&gt;curl&lt;/code&gt; build with ML-KEM support. Measure connection-establishment latency; for a typical handshake the additional ~5-10 ms is in the noise (see the §9 PQC handshake budget Callout for the TPM-bound exception).&lt;/p&gt;

The `X25519MLKEM768` named group has IANA codepoint `0x11ec`. A Wireshark display filter of `tls.handshake.extensions_key_share_group == 0x11ec` flags handshakes that negotiated the hybrid. Combined with `tls.handshake.version == 0x0304`, you can quickly spot whether a peer actually used the PQC hybrid or fell back to plain X25519.
&lt;h3&gt;Common pitfalls&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Client&lt;/code&gt; vs &lt;code&gt;Server&lt;/code&gt; asymmetry.&lt;/strong&gt; Two sub-keys, two hives, four registry edits per protocol version. Tooling like &lt;code&gt;IISCrypto&lt;/code&gt; automates the matrix; doing it by hand is the most common source of &quot;we thought we disabled TLS 1.0 but our outbound WinHTTP still negotiates it&quot; tickets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SCH_USE_STRONG_CRYPTO&lt;/code&gt;&lt;/strong&gt; -- the SCHANNEL_CRED flag is per-call, not per-machine. .NET sets it by default on modern targets but historically didn&apos;t on .NET Framework 4.5.x. If you maintain old .NET Framework workloads, audit them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SSLKEYLOGFILE&lt;/code&gt;&lt;/strong&gt; -- SChannel does not export keys to &lt;code&gt;SSLKEYLOGFILE&lt;/code&gt;. Wireshark cannot decrypt SChannel-served TLS traffic without separate key extraction (etw-based, or a TLS-terminating proxy). Plan your packet-capture strategy accordingly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the practical guide is the &quot;what to do,&quot; the FAQ that follows is the &quot;what to stop believing.&quot;&lt;/p&gt;
&lt;h2&gt;13. Frequently Asked Questions&lt;/h2&gt;

No. They were different bugs, different stacks, different vendors. **Heartbleed (CVE-2014-0160), April 7, 2014, was a flaw in OpenSSL&apos;s TLS Heartbeat extension code path** [@nvd-cve-2014-0160]. **MS14-066 / CVE-2014-6321 (&quot;WinShock&quot;), November 11, 2014, was a pre-authentication remote code execution in SChannel&apos;s TLS message-parsing path** [@ms14-066][@nvd-cve-2014-6321], disclosed under coordinated vulnerability disclosure and credited by IBM X-Force to researcher Robert Freeman. SChannel does not implement OpenSSL&apos;s Heartbeat code and was not affected by Heartbleed; Microsoft confirmed this publicly in April 2014. The two events have been blended in many secondary accounts since.

No. CVE-2014-6321 had a public Patch Tuesday bulletin (MS14-066, November 11, 2014) [@ms14-066], a US-CERT alert (TA14-318A, November 18, 2014) [@uscert-ta14-318a], and a CERT/CC vulnerability note (VU#505120) [@certcc-vu505120]. The &quot;silently patched&quot; framing in some accounts refers to the *additional* SChannel hardening fixes Microsoft bundled into the same KB without separate bulletins, not to the headline CVE itself. This article does not assign specific CVE IDs to those bundled extras.

No. **ZeroLogon affected the Netlogon Remote Protocol (MS-NRPC), implemented in `netlogon.dll`**. The &quot;Netlogon secure channel&quot; and the &quot;SChannel SSP&quot; (`schannel.dll`, the TLS provider this article is about) share a name root but are different protocols, different DLLs, different code paths, and different bug classes. Confusing the two is one of the most common Windows-security naming traps.

Yes -- they are complementary, not alternatives [@itm4n-runasppl]. **PPL is a same-privilege gate inside Virtual Trust Level 0 (VTL0)**: it stops a non-PPL process from opening LSASS for memory read [@ms-learn-lsa-protection]. **Credential Guard moves credential material into the `LSAIso` trustlet at VTL1**, behind the VBS / Hyper-V boundary [@ms-learn-credential-guard]. They protect against different threats and stack rather than substitute.

No. The Microsoft Learn page for the CNG ML-KEM API carries an explicit &quot;prerelease product / Windows Insider Preview&quot; banner as of mid-2026 [@ms-learn-cng-mlkem-examples]. The primitive ships in SymCrypt v103.5.0 and later [@symcrypt-changelog]; the CNG and SChannel surfaces are rolling through the Insider channel. Track the Microsoft Tech Community PQC posts for OS-channel GA announcements [@ms-tech-community-pqc][@ms-tech-community-pqc-companion].

On Windows, yes. On Linux .NET delegates `SslStream` to OpenSSL; on macOS it uses Apple&apos;s Network framework. PQC support follows the underlying stack, so the same .NET binary&apos;s TLS posture differs by host OS in mid-2026.

Because the *agility property* the article is about is anchored to CNG, which shipped in Vista in January 2007 [@ms-learn-cng-portal] -- about nineteen years to mid-2026. Pre-CNG SChannel was not algorithm-agile in any meaningful sense: primitives were baked into CryptoAPI 1.0 CSP DLLs, ECC could not be expressed in the `ALG_ID + key BLOB` model at all, and adding a new algorithm required a CSP rev plus an OS release. CNG is when &quot;rotate every cipher&quot; stopped being a slogan and started being a property the substrate could deliver. The thirty-year framing would be arithmetically accurate but argumentatively wrong.
&lt;p&gt;The Microsoft TLS stack has spent twenty years proving that one architectural decision -- decouple algorithms from DLLs, addressed by string identifier through a stable provider model -- can carry a vendor through every primitive rotation cryptography throws at it. The receipts now include a post-quantum hybrid key exchange that runs through the same dispatch path Vista shipped in 2007. The next test, the signature-side PQC transition, is already in flight inside SymCrypt. Whatever the world chooses to do with those primitives, the substrate is ready.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;schannel-twenty-year-algorithm-agility&quot; keyTerms={[
  { term: &quot;SChannel SSP&quot;, definition: &quot;The Microsoft Security Support Provider (schannel.dll) implementing SSL, TLS, and DTLS on Windows.&quot; },
  { term: &quot;CNG (Cryptography API: Next Generation)&quot;, definition: &quot;Vista-era substrate replacing CryptoAPI 1.0; splits into BCrypt (primitives) and NCrypt (key custodians).&quot; },
  { term: &quot;BCrypt&quot;, definition: &quot;CNG API for primitive cryptographic operations addressed by string algorithm identifier.&quot; },
  { term: &quot;NCrypt&quot;, definition: &quot;CNG API for key custodians via pluggable Key Storage Providers (software, smart card, TPM, HSM).&quot; },
  { term: &quot;KSP (Key Storage Provider)&quot;, definition: &quot;Pluggable NCrypt module that owns a private key&apos;s lifecycle and operations.&quot; },
  { term: &quot;SymCrypt&quot;, definition: &quot;Microsoft&apos;s unified FIPS-validated primitive engine started by Niels Ferguson in late 2006; open-sourced under MIT in July 2019.&quot; },
  { term: &quot;AEAD&quot;, definition: &quot;Authenticated Encryption with Associated Data; single-pass encrypt-and-authenticate construction that eliminates mac-then-encrypt padding-oracle bugs.&quot; },
  { term: &quot;ECDHE&quot;, definition: &quot;Ephemeral Elliptic-Curve Diffie-Hellman; provides forward secrecy by deriving each session key from a fresh elliptic-curve key pair.&quot; },
  { term: &quot;ML-KEM&quot;, definition: &quot;FIPS 203 module-lattice-based Key Encapsulation Mechanism; the NIST-standardised PQC KEM that X25519MLKEM768 wraps.&quot; },
  { term: &quot;PPL (Protected Process Light)&quot;, definition: &quot;Same-privilege process-protection gate inside VTL0; blocks non-PPL callers from opening LSASS for memory read when RunAsPPL is enabled.&quot; },
  { term: &quot;CertGetCertificateChain&quot;, definition: &quot;The Windows chain-building API that walks from leaf to trusted root with revocation, name-constraint, and EKU enforcement.&quot; },
  { term: &quot;MS14-066 / WinShock&quot;, definition: &quot;Pre-authentication SChannel parsing-path RCE (CVE-2014-6321), patched November 11, 2014; not Heartbleed.&quot; }
]} questions={[
  { q: &quot;Name the structural property of CryptoAPI 1.0 that prevented ECC from being expressed.&quot;, a: &quot;The ALG_ID + key BLOB model could not represent named curves, parameter sets, or point compression -- ECC&apos;s identity is per-curve, not per-algorithm-type.&quot; },
  { q: &quot;What two API splits did CNG introduce, and what does each abstract?&quot;, a: &quot;BCrypt abstracts primitives (algorithms by string identifier); NCrypt abstracts key custodians (via Key Storage Providers).&quot; },
  { q: &quot;Why is the &apos;twenty-year algorithm-agility&apos; frame anchored to 2007 rather than 1996?&quot;, a: &quot;CNG (2007) is when algorithm agility became a first-class substrate property. Pre-CNG SChannel was protocol-agile (it could rotate SSL/TLS versions) but not primitive-agile -- adding a new primitive required a CSP DLL rev.&quot; },
  { q: &quot;What is the SChannel-side disambiguation between MS14-066 and Heartbleed?&quot;, a: &quot;MS14-066 was a pre-auth RCE in SChannel&apos;s TLS parsing path (Microsoft, November 2014). Heartbleed was an OpenSSL Heartbeat over-read (April 2014). SChannel does not implement the OpenSSL Heartbeat code path.&quot; },
  { q: &quot;What concrete handshake-time data does the X25519MLKEM768 named group concatenate?&quot;, a: &quot;ClientHello key_share is mlkem_pk || x25519_pk (1216 B); ServerHello key_share is mlkem_ct || x25519_pk (1120 B); both sides derive shared_secret = mlkem_ss || x25519_ss (64 B) and feed it to TLS 1.3&apos;s HKDF-Extract.&quot; },
  { q: &quot;Why are PPL (RunAsPPL) and Credential Guard described as complementary rather than substitutes?&quot;, a: &quot;PPL is a same-privilege gate inside VTL0 (it blocks OpenProcess(LSASS, VM_READ) from non-PPL callers). Credential Guard moves credential material into the LSAIso trustlet at VTL1 behind the VBS / Hyper-V boundary. They protect against different attack classes.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows-security</category><category>tls</category><category>cryptography</category><category>post-quantum</category><category>schannel</category><category>cng</category><category>algorithm-agility</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>Post-Quantum Cryptography on Windows: The Thirty-Year Migration That Just Arrived</title><link>https://paragmali.com/blog/post-quantum-cryptography-on-windows-the-thirty-year-migrati/</link><guid isPermaLink="true">https://paragmali.com/blog/post-quantum-cryptography-on-windows-the-thirty-year-migrati/</guid><description>How NIST FIPS 203/204/205 reaches the Windows platform via SymCrypt, CNG, Schannel, and .NET 10 -- the algorithm internals, the wire format, the migration timeline, and the honest accounting.</description><pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate><content:encoded>
**Post-quantum cryptography arrived on Windows in 2024-2026.** NIST finalised FIPS 203 (ML-KEM), FIPS 204 (ML-DSA), and FIPS 205 (SLH-DSA) on August 13, 2024 [@nist-fips-approved-news]. SymCrypt has shipped ML-KEM, ML-DSA, LMS, and composite-ML-KEM implementations across versions 103.5.0 through 103.11.0; CNG exposes them as `BCRYPT_MLKEM_ALG_HANDLE` and `BCRYPT_MLDSA_ALGORITHM`; Schannel can negotiate hybrid TLS 1.3 `X25519MLKEM768` (codepoint 0x11EC) on 24H2 behind Group Policy [@symcrypt-changelog, @cng-mlkem-examples, @draft-tls-ecdhe-mlkem]. The migration closes the harvest-now-decrypt-later channel for TLS-protected traffic, leaves the signed-binary persistence channel open, and is structurally constrained by the 4096-byte TPM 2.0 command buffer against which ML-DSA-87&apos;s 4595-byte signatures overflow [@fips-204-pdf, @wolfssl-wolftpm-v185].
&lt;h2&gt;1. The 1184-Byte Field&lt;/h2&gt;
&lt;p&gt;A Windows endpoint opens a connection to &lt;code&gt;cloudflare.com&lt;/code&gt;. In its ClientHello, alongside the 32-byte X25519 public value every TLS 1.3 handshake has carried since 2018, sits a new 1184-byte field whose contents look like uniform noise -- an ML-KEM-768 encapsulation key, the bytes by which Microsoft, Cloudflare, Google, Apple, and OpenSSH have chosen to close a future they cannot yet see [@draft-tls-ecdhe-mlkem, @cloudflare-pq-2024].&lt;/p&gt;
&lt;p&gt;Two adversaries are watching the handshake. The first has 2026 compute and cannot break either share. The second has a hypothetical 2040 fault-tolerant quantum computer, breaks the X25519 share trivially via Shor&apos;s algorithm, and walks away unable to recover the ML-KEM-768 session key. Why does the handshake hold against the second adversary, and what did it take to make that field 1184 bytes long?&lt;/p&gt;

A family of cryptographic algorithms whose security rests on mathematical problems for which no efficient quantum algorithm is known. PQC is a public-key replacement programme: it replaces RSA, Diffie-Hellman, and elliptic-curve discrete-log primitives that Shor&apos;s algorithm collapses in polynomial time on a fault-tolerant quantum computer. Symmetric primitives (AES, SHA-2/3) survive with parameter increases and are not the target of PQC standardisation.
&lt;p&gt;The wire format is concrete and currently shipping. The IETF draft &lt;code&gt;draft-ietf-tls-ecdhe-mlkem-04&lt;/code&gt; (published 8 February 2026) defines three hybrid Supported Groups codepoints in TLS 1.3: &lt;code&gt;X25519MLKEM768&lt;/code&gt; at 0x11EC, &lt;code&gt;SecP256r1MLKEM768&lt;/code&gt; at 0x11EB, and &lt;code&gt;SecP384r1MLKEM1024&lt;/code&gt; at 0x11ED [@draft-tls-ecdhe-mlkem, @iana-tls-parameters]. The ClientHello &lt;code&gt;key_share&lt;/code&gt; extension carries 32 bytes of X25519 public value followed by 1184 bytes of ML-KEM-768 encapsulation key. The ServerHello reply carries 32 bytes of X25519 public value followed by 1088 bytes of ML-KEM-768 ciphertext. Both endpoints derive an X25519 shared secret and an ML-KEM-768 shared secret, concatenate them, and feed both into TLS 1.3&apos;s HKDF-Extract per &lt;code&gt;draft-ietf-tls-hybrid-design-16&lt;/code&gt; [@draft-tls-hybrid]. An adversary who can break either component but not both still learns nothing.&lt;/p&gt;

A threat model in which an adversary records today&apos;s network traffic and stores it for years, decrypting it once a sufficiently capable quantum computer is available. The threat applies to any traffic whose secrecy must survive past the time-to-cryptographically-relevant-quantum-computer; it does not apply to signed-binary integrity, which is validated at load time. Hybrid TLS shifts the boundary from &quot;must trust X25519 forever&quot; to &quot;must trust either X25519 or ML-KEM-768 forever&quot; [@cloudflare-pq-2024, @mosca-2015].
&lt;p&gt;The first internet-scale deployment of the construction landed on October 3, 2022, when Cloudflare turned on hybrid post-quantum key agreement by default for every website and API on its edge [@cloudflare-pq-for-all].Cloudflare&apos;s blog post measured the bytes-on-the-wire cost of the deployment as roughly 1.1 KB per handshake added; by March 2024 nearly two percent of all TLS 1.3 connections to Cloudflare&apos;s edge negotiated post-quantum key agreement, with double-digit adoption forecast by year-end [@cloudflare-pq-2024]. The Cloudflare default-on date predated FIPS 203&apos;s August 2024 finalisation by almost two years, which is why early deployments speak of &quot;Kyber&quot; and &quot;X25519Kyber768Draft00&quot; rather than ML-KEM.&lt;/p&gt;
&lt;p&gt;Apple&apos;s iMessage PQ3 followed in February 2024, framed as &quot;Level 3&quot; -- post-quantum key establishment plus post-quantum ratcheting [@apple-imessage-pq3]. By May 2026, Microsoft, Google, OpenSSH, and Signal have all shipped or announced hybrid post-quantum key agreement; Section 7 catalogues the per-vendor deployments verbatim, anchored to each vendor&apos;s own release artifact [@cloudflare-pq-2024, @signal-pqxdh, @openssh-9-9].&lt;/p&gt;
&lt;p&gt;This article delivers two promises. The first is algorithm-level: by the end of Section 5 you will know ML-KEM, ML-DSA, and SLH-DSA well enough to reason about parameter-set choices, side-channel posture, and FIPS-mandated byte counts. The second is platform-level: by the end of Section 6 you will know which CNG identifier ships in which SymCrypt release, which Schannel toggle gates X25519MLKEM768 on 24H2, and which Windows surfaces (Schannel, AD CS, .NET 10, Azure Key Vault) carry PQC in May 2026 and which (IKEv2, SMB, RDP, &lt;a href=&quot;https://paragmali.com/blog/bitlocker-on-windows-architecture-attacks-and-the-limits-of-/&quot; rel=&quot;noopener&quot;&gt;BitLocker network unlock&lt;/a&gt;, Kerberos PKINIT, &lt;a href=&quot;https://paragmali.com/blog/your-face-is-not-your-password-inside-windows-hellos-hardwar/&quot; rel=&quot;noopener&quot;&gt;Windows Hello attestation&lt;/a&gt;) do not.&lt;/p&gt;
&lt;p&gt;Every line of code, every parameter set, every byte of that 1184-byte field has a thirty-year story behind it. To understand what shipped, we start where it began -- with a 1994 paper that put a clock on every public-key cryptosystem then in production.&lt;/p&gt;
&lt;h2&gt;2. Historical Origins&lt;/h2&gt;
&lt;p&gt;Why is replacing public-key cryptography hard? Because in 1976, Whitfield Diffie and Martin Hellman defined the primitive that &lt;em&gt;everything since&lt;/em&gt; has imitated. Their &quot;New Directions in Cryptography&quot; paper, in &lt;em&gt;IEEE Transactions on Information Theory&lt;/em&gt; 22(6), introduced the asymmetric key-agreement model [@dh-1976]: two parties exchange public values, derive a shared secret, and never share the underlying private state. The shared secret was the discrete logarithm of a public element in a finite group. Every public-key construction that followed -- RSA (1977), the Diffie-Hellman variants, DSA (1991), ECDSA and the elliptic-curve variants (mid-1980s into the 1990s, with X25519 standardised in RFC 7748 in 2016) -- inherited one of two hard problems: integer factoring, or the discrete logarithm in some abelian group [@rfc-7748].&lt;/p&gt;
&lt;p&gt;Eighteen years later, Peter Shor at Bell Labs found a polynomial-time quantum algorithm for both [@shor-1996]. The arXiv preprint &lt;code&gt;quant-ph/9508027&lt;/code&gt; dates to August 1995; the journal version appeared as &quot;Polynomial-Time Algorithms for Prime Factorization and Discrete Logarithms on a Quantum Computer&quot; in &lt;em&gt;SIAM Journal on Computing&lt;/em&gt; 26(5) (1997) 1484-1509; DOI 10.1137/S0097539795293172. Shor&apos;s algorithm requires a fault-tolerant quantum computer with thousands of logical qubits -- the kind of machine that does not yet exist, and may never exist in some accounts. But if it does exist, RSA, DH, DSA, ECDSA, and ECDH all collapse simultaneously. Not weakened; &lt;em&gt;broken&lt;/em&gt;. Doubling key sizes does not help; the algorithm&apos;s runtime is polynomial in the key length.&lt;/p&gt;

A polynomial-time quantum algorithm, due to Peter Shor (1994-1996), that solves integer factoring and the discrete logarithm in arbitrary abelian groups [@shor-1996]. The algorithm reduces both problems to finding the period of a function via the Quantum Fourier Transform, which a fault-tolerant quantum computer can compute in time polynomial in the input size. RSA, finite-field Diffie-Hellman, DSA, and the elliptic-curve variants ECDH/ECDSA/X25519 are all structurally retired by Shor&apos;s algorithm; no parameter increase rescues them.
&lt;p&gt;Two years later, Lov Grover (also at Bell Labs) published the symmetric-key counterpart. Grover&apos;s algorithm searches an unstructured database of N items in O(sqrt(N)) quantum steps [@grover-1996]. Applied to AES-128, Grover reduces the effective key strength to roughly $2^{64}$ quantum-search steps -- comparable to a 64-bit symmetric key. Applied to AES-256, it leaves 128 bits of security. The asymmetric lane is fatal; the symmetric lane is a parameter bump. This is why the entire post-quantum programme is a &lt;em&gt;public-key&lt;/em&gt; replacement programme, not a symmetric one.The standard policy response to Grover is to double the symmetric key size. AES-256 retains 128 bits of post-quantum security; SHA-384 retains 192 bits of preimage resistance; SHA-512 retains 256 bits. CNSA 2.0 mandates AES-256 and SHA-384 specifically for this reason [@cnsa20-csa]. Grover-style speedups do not generalise to AEAD constructions in the same way the asymmetric collapse does; the cost of doubling is structural and easy to absorb, which is why no one tries to invent a &quot;post-quantum AES.&quot;&lt;/p&gt;
&lt;p&gt;If Shor and Grover are 1994-1996 results, why is replacing public-key cryptography not a 2040 problem? Michele Mosca&apos;s 2015 ePrint 2015/1075 named the deadline. Mosca&apos;s inequality is one line:&lt;/p&gt;
&lt;p&gt;$$X + Y &amp;gt; Z$$&lt;/p&gt;
&lt;p&gt;where X is the security shelf-life of the data (how long today&apos;s traffic must remain confidential), Y is the migration time (how long it takes to deploy quantum-safe systems), and Z is the time until a cryptographically relevant quantum computer arrives. If X + Y exceeds Z, the adversary harvesting traffic today wins regardless of when the quantum computer arrives [@mosca-2015].&lt;/p&gt;

The deadline relation $X + Y &amp;gt; Z$: if data-secrecy lifetime (X) plus migration time (Y) exceeds time-to-quantum-computer (Z), harvest-now-decrypt-later succeeds. Mosca&apos;s framing turned an open quantum-engineering timeline into an actionable IT-policy lever; if you cannot predict Z, you must minimise Y, which means starting migration now [@mosca-2015].

If the security shelf-life of your data plus the migration time to deploy quantum-safe systems exceeds the time-to-quantum-computer, the adversary harvesting traffic today wins. -- the X + Y &amp;gt; Z framing, Mosca (eprint 2015/1075).
&lt;p&gt;On September 7, 2022, the U.S. National Security Agency turned Mosca&apos;s inequality into national-security policy. The Commercial National Security Algorithm Suite 2.0 (CNSA 2.0) is the algorithm list the NSA requires for protecting U.S. National Security Systems [@nsa-cnsa-news, @cnsa20-csa]. The current revision (May 30, 2025) names ML-KEM-1024 for key establishment, ML-DSA-87 for general digital signatures, LMS and XMSS for firmware signing, AES-256 for symmetric encryption, and SHA-384 for hashing. The policy carries four dates that drive every U.S. vendor roadmap including Microsoft&apos;s: acquisition preference for PQC in new National Security Systems by January 1, 2027; legacy-algorithm phase-out beginning December 31, 2030; mandatory PQC adoption by December 31, 2031; and disallowance of RSA / ECDSA after 2035.&lt;/p&gt;
&lt;p&gt;Shor&apos;s algorithm requires a fault-tolerant quantum computer that does not yet exist. So why isn&apos;t the migration easy? Because cryptographers tried to replace the asymmetric primitive for thirty years before this paper -- and every early attempt failed in a different way.&lt;/p&gt;
&lt;h2&gt;3. Early Approaches and Their Failures&lt;/h2&gt;
&lt;p&gt;Three rejected family trees and one almost-survivor explain why ML-KEM looks the way it does in 2026. Each was tried; each failed in a specific way; the failure shaped what survived.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;McEliece (1978)&lt;/strong&gt; is the oldest post-quantum proposal still under active study. Robert McEliece&apos;s construction uses the hardness of decoding a general linear code -- specifically, a binary Goppa code disguised by random permutations and a scrambling matrix [@mceliece-1978]. The cryptosystem has survived forty-eight years of cryptanalysis with no structural break; its security argument is one of the most conservative in cryptography. The cost is the public key. Classic McEliece at NIST security category 1 has public keys of roughly 261 kilobytes; at category 5, about 1 megabyte [@mceliece-project]. That size makes it unusable in TLS, where the entire ClientHello must fit in one or two IP packets. Classic McEliece survives as a Round-4 NIST candidate; it was &lt;em&gt;not&lt;/em&gt; selected for FIPS standardisation because of the key-size constraint, but is widely cited as the conservative fallback for long-term archival key wrapping.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HFE and multivariate cryptography (1996)&lt;/strong&gt; form the most thoroughly broken family. Jacques Patarin&apos;s Hidden Field Equations (HFE) hide the structure of a univariate polynomial over a small extension field by composing with random linear transformations on each side. Kipnis and Shamir broke the original HFE construction in 1999 [@kipnis-shamir-1999]. The descendant scheme Rainbow advanced through three NIST rounds before Ward Beullens published &quot;Breaking Rainbow Takes a Weekend on a Laptop&quot; in eprint 2022/214 on 25 February 2022, recovering Rainbow&apos;s secret key in 53 hours on a commodity laptop [@beullens-rainbow-2022].53 hours on a commodity laptop is the visceral data point. Rainbow had been a NIST third-round signature finalist; one paper, one weekend of CPU time, retired it. Beullens&apos; result is now the canonical example in PQC pedagogy of how a cryptographic finalist can be retired by an algorithmic insight that nobody noticed during seven years of NIST evaluation. The multivariate signature lane is effectively closed in 2026, with the partial exception of small specialised constructions (UOV-style schemes) that NIST is considering in the additional-signatures onramp [@nist-pqc-dig-sig].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NTRU (1996)&lt;/strong&gt; is the founding lattice cryptosystem. Jeffrey Hoffstein, Jill Pipher, and Joseph Silverman presented &quot;NTRU: A ring-based public key cryptosystem&quot; at ANTS-III in 1998 [@ntru-1996]. The construction works in a polynomial ring $R = \mathbb{Z}[X]/(X^n - 1)$ and offers public keys of roughly 1-2 kilobytes -- the first lattice cryptosystem with sizes competitive with RSA. NTRU was patent-encumbered for two decades (US Patents 6,081,597 and 6,144,740 expired in August and November 2017) [@ntru-patents], which kept it out of standards work for the formative years. Falcon, the NIST-selected lattice signature scheme that became FIPS 206 draft, inherits the NTRU lattice structure directly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SIDH and SIKE (2011-2022)&lt;/strong&gt; were the most efficient post-quantum proposal by public-key size. Supersingular Isogeny Diffie-Hellman, introduced by Jao and De Feo in 2011 [@jao-defeo-2011], achieved public keys of roughly 330 bytes at category 1 [@wp-sidh] -- smaller than ML-KEM-512&apos;s 800 bytes. NIST advanced SIKE to the fourth round of evaluation on 5 July 2022 [@nist-pqc-selection-2022]. On 27 July 2022, twenty-two days later, Wouter Castryck and Thomas Decru published &quot;An efficient key recovery attack on SIDH,&quot; recovering SIKEp434&apos;s secret key in about ten minutes on a single CPU core via a torsion-point exploitation of Kani&apos;s reducibility criterion [@castryck-decru-sidh]. The higher-security parameter set SIKEp751 (NIST category 5) fell in roughly three hours on the same hardware. A concurrent paper by Maino and Martindale extended the attack to arbitrary starting curves [@maino-martindale-sidh]. One paper, one month, the entire isogeny lane retired. SIKE is the canonical example of why NIST&apos;s portfolio rests on multiple unrelated hardness assumptions.&lt;/p&gt;

flowchart TD
    PQ[&quot;Post-Quantum Cryptography&quot;]
    PQ --&amp;gt; Lat[&quot;Lattice&lt;br /&gt;(LWE, Module-LWE, NTRU)&quot;]
    PQ --&amp;gt; Code[&quot;Code-based&lt;br /&gt;(Goppa, Quasi-Cyclic)&quot;]
    PQ --&amp;gt; Multi[&quot;Multivariate&lt;br /&gt;(HFE, Rainbow)&quot;]
    PQ --&amp;gt; Hash[&quot;Hash-based&lt;br /&gt;(XMSS, LMS, SPHINCS+)&quot;]
    PQ --&amp;gt; Iso[&quot;Isogeny&lt;br /&gt;(SIDH, SIKE)&quot;]
    Lat --&amp;gt; LatV[&quot;ACTIVE: ML-KEM, ML-DSA, Falcon&quot;]
    Code --&amp;gt; CodeV[&quot;NICHE: HQC, Classic McEliece&quot;]
    Multi --&amp;gt; MultiV[&quot;DEAD: Rainbow broken 2022&quot;]
    Hash --&amp;gt; HashV[&quot;ACTIVE: SLH-DSA, LMS, XMSS&quot;]
    Iso --&amp;gt; IsoV[&quot;DEAD: SIDH/SIKE broken 2022&quot;]

A proof technique, introduced for lattices by Miklos Ajtai in 1996 and refined for LWE by Oded Regev in 2005, that ties the average-case security of a cryptosystem to the worst-case hardness of an underlying lattice problem [@regev-2005]. The reduction says: solving random instances of the cryptosystem at any non-negligible advantage gives an algorithm for the *worst-case* hard problem. RSA has no analogous reduction; the average factoring instance is conjectured hard, but no theorem ties it to worst-case factoring. The lattice reduction is the structural argument for why post-quantum lattice cryptography may be more conservative, in a formal sense, than RSA.
&lt;p&gt;The portfolio lesson lands here, and it is the article&apos;s first aha moment. Post-quantum cryptography is not a single family; it is a &lt;em&gt;portfolio&lt;/em&gt; across multiple hardness assumptions, because each one has been broken at least once during the modern standardisation effort. The Rainbow break and the SIKE break both happened &lt;em&gt;during&lt;/em&gt; the NIST competition, in 2022, on candidates that NIST had advanced for further study. This is why the eventual slate -- ML-KEM (lattice) plus SLH-DSA (hash) -- sits on &lt;em&gt;two structurally unrelated&lt;/em&gt; foundations. A single mathematical break cannot retire the whole programme.&lt;/p&gt;
&lt;p&gt;Lattices survived. But the lattices of 2005 had megabyte-scale public keys, unusable in TLS. How those keys were compressed to kilobytes is the story of the next section.&lt;/p&gt;
&lt;h2&gt;4. The Evolution -- Lattices in Five Generations&lt;/h2&gt;
&lt;p&gt;In 2005, Oded Regev published a paper that gave lattice cryptography the mathematical foundation RSA never had. By 2010, the same idea had been compressed by a factor of &lt;code&gt;n&lt;/code&gt; via the Number Theoretic Transform; by 2015 it had been generalised with a parameter knob that let one base ring serve every security category; by 2024 it was a Federal Information Processing Standard. This section walks the generation-by-generation story of how lattices got from impossible to inevitable.&lt;/p&gt;
&lt;h3&gt;Generation 0 (1976-1994): the classical baseline&lt;/h3&gt;
&lt;p&gt;Diffie-Hellman, RSA, DSA, ECDH, ECDSA. Five primitives over four decades, all on discrete-log-style hardness in one group or another, all retired in one stroke by Shor&apos;s algorithm. The classical baseline is what PQC replaces. Nothing about post-quantum cryptography innovates on the symmetric side; AES and SHA-2 survive with parameter increases.&lt;/p&gt;
&lt;h3&gt;Generation 1 (1996-2009): plural hard problems, mostly impractical&lt;/h3&gt;
&lt;p&gt;Miklos Ajtai&apos;s 1996 STOC paper &quot;Generating Hard Instances of Lattice Problems&quot; introduced the first worst-case-to-average-case reduction for a lattice problem (the Short Integer Solution problem) [@ajtai-1996]. The reduction was a foundational theoretical result; the cryptographic constructions built from it had public keys in the megabytes.&lt;/p&gt;
&lt;p&gt;Nine years later, Oded Regev published &quot;On Lattices, Learning with Errors, Random Linear Codes, and Cryptography&quot; at STOC 2005 [@regev-2005]. The Learning With Errors problem is simple to state.&lt;/p&gt;

Given a uniformly random matrix $A \in \mathbb{Z}_q^{m \times n}$, a secret vector $s \in \mathbb{Z}_q^n$, and a small noise vector $e$ sampled from a Gaussian-like distribution, distinguish the pair $(A, As + e)$ from a uniformly random pair $(A, b)$ where $b$ is uniform in $\mathbb{Z}_q^m$. LWE is conjectured hard for any polynomial-time algorithm classical or quantum; Regev&apos;s theorem ties LWE to the worst-case hardness of approximating shortest-vector problems on $n$-dimensional lattices, via a quantum reduction [@regev-2005].
&lt;p&gt;LWE was the cryptographic breakthrough. The construction was clean, the reduction tied average-case security to worst-case lattice hardness, and the resulting cryptosystem was simple enough that any cryptographer could implement it. But the public key was a full $n \times n$ matrix over $\mathbb{Z}_q$ -- $O(n^2 \log q)$ bits. At the parameter sizes needed for 128-bit security, that meant several megabytes of public key. Unusable in TLS, unusable in X.509, unusable in any deployment that touches the wire.&lt;/p&gt;
&lt;h3&gt;Generation 2 (2010-2017): the ring-LWE and module-LWE compression&lt;/h3&gt;
&lt;p&gt;The compression that made lattices deployable was a single algebraic move. Lift LWE from $\mathbb{Z}_q$ to a polynomial ring. Lyubashevsky, Peikert, and Regev&apos;s 2010 paper &quot;On Ideal Lattices and Learning with Errors over Rings&quot; (eprint 2012/230) introduced Ring-LWE [@lpr-2010-ringlwe]. The underlying ring is $R_q = \mathbb{Z}_q[X]/(X^n + 1)$ for $n$ a power of two; the secret and noise are now polynomials in $R_q$ rather than vectors over $\mathbb{Z}_q$. Multiplying two ring elements becomes a polynomial multiplication, which the Number Theoretic Transform reduces from $O(n^2)$ scalar multiplications to $O(n \log n)$.&lt;/p&gt;

A discrete Fourier transform over a finite field rather than the complex numbers. For a prime $q$ such that $2n$ divides $q - 1$, NTT converts a polynomial $a(X) \in \mathbb{Z}_q[X]/(X^n + 1)$ into its evaluations at the $2n$-th roots of unity in $\mathbb{Z}_q$. Polynomial multiplication then becomes pointwise multiplication of the NTT vectors. NTT is the speedup that compresses Ring-LWE arithmetic from $O(n^2)$ to $O(n \log n)$ and is the reason ML-KEM-768 encapsulates in tens of microseconds on commodity x86-64 [@fips-203-pdf].
&lt;p&gt;Public keys dropped from megabytes to kilobytes. The 2010 lift is the load-bearing intellectual move; everything subsequent is engineering.&lt;/p&gt;
&lt;p&gt;Adeline Langlois and Damien Stehle&apos;s 2012/2015 Module-LWE paper added a parameter knob [@langlois-stehle-modulelwe]. Module-LWE works over $R_q$ rings of &lt;em&gt;fixed&lt;/em&gt; degree $n$ (typically 256 in ML-KEM), but lifts the secret and matrix into module rank $k$: $A$ is a $k \times k$ matrix of ring elements, $s$ is a $k$-vector of ring elements. Now one base ring of degree 256 can serve every NIST security category by varying $k \in {2, 3, 4}$. ML-KEM-512 uses $k = 2$; ML-KEM-768 uses $k = 3$; ML-KEM-1024 uses $k = 4$. The compiler-style metaphor is exact: Ring-LWE was an over-fitted special case, Module-LWE generalises it.&lt;/p&gt;

A generalisation of Learning With Errors over polynomial rings of fixed degree, in which the secret is a $k$-vector of ring elements and the matrix is $k \times k$. Module-LWE inherits the worst-case-to-average-case reduction from Ring-LWE [@langlois-stehle-modulelwe], offers a finer-grained security knob than either LWE or Ring-LWE, and is the underlying hardness assumption of ML-KEM (FIPS 203) and ML-DSA (FIPS 204) [@fips-203-pdf, @fips-204-pdf].
&lt;p&gt;The first TLS deployment of a Ring-LWE key exchange landed in 2014, and Microsoft Research was at the centre of it.&lt;/p&gt;

The BCNS 2014 paper &quot;Post-quantum key exchange for the TLS protocol from the ring learning with errors problem&quot; by Joppe Bos, Craig Costello, Michael Naehrig, and Douglas Stebila (eprint 2014/599) was the first end-to-end TLS implementation of a Ring-LWE key exchange [@bcns-2014]. Two of the four authors -- Costello and Naehrig -- were Microsoft Research Redmond. Two years later, the same Microsoft Research group plus collaborators published Frodo (CCS 2016, eprint 2016/659), the unstructured-LWE conservative fallback design with no ring algebra [@frodo-2016]. Frodo became FrodoKEM in the NIST process; FrodoKEM was selected as a Round-3 alternate but not advanced to standardisation [@frodokem-project]. Microsoft Research&apos;s own retrospective spans this work: &quot;Our PQC effort began in 2014 when we published research on post-quantum algorithms and later quantum cryptanalysis ... we participated in four submissions to the original 2017 NIST PQC call and one submission to the current call. Since 2018 we have been experimenting with verified versions of PQC algorithms and in 2019 Microsoft Research completed testing of an experimental PQC-protected VPN tunnel between Redmond, Washington, and Scotland&quot; [@ms-quantum-safe-blog]. The 2024 FIPS publication did not surprise Microsoft.
&lt;p&gt;Google&apos;s Chrome team deployed the construction in production first. CECPQ1 (&quot;Combined Elliptic-Curve and Post-Quantum 1&quot;) shipped in Chrome Canary in July 2016, combining X25519 with NewHope [@google-cecpq1]. NewHope was a Ring-LWE construction by Alkim, Ducas, Poppelmann, and Schwabe; CECPQ1 ran for several months as an experiment, measured the cost of an extra ~2 KB on each handshake, and was retired. CECPQ2 replaced it with NTRU-HRSS -- the announcement post by Adam Langley names the lineage explicitly (&quot;CECPQ1 was the experiment ... It&apos;s about time for CECPQ2&quot;) and the NTRU-HRSS basis -- and was wound down in 2022 as Chrome migrated to the X25519+Kyber-768 hybrid following NIST&apos;s July 2022 selection [@cloudflare-pq-2024]. The parallel CECPQ2b experiment paired X25519 with SIKE; the Castryck-Decru break that same month retired CECPQ2b along with the entire isogeny lane. The Cloudflare-Microsoft-Google triad has been iterating in production since.&lt;/p&gt;
&lt;h3&gt;Generation 3 (2017-2022): the NIST competition&lt;/h3&gt;
&lt;p&gt;NIST issued the formal call for post-quantum public-key submissions in December 2016. Eighty-two submissions arrived by the November 2017 deadline; sixty-nine were judged complete and proper, advancing into Round 1 (announced December 2017; narrowed to 26 in Round 2, January 2019) [@wp-nist-pqc].The 82-vs-69 discrepancy is a frequent source of confusion in PQC pedagogy. Eighty-two total submissions, sixty-nine deemed &quot;complete and proper&quot; by NIST&apos;s intake review, advanced to Round 1. The remaining thirteen had documentation defects or were withdrawn. Wikipedia&apos;s &quot;NIST Post-Quantum Cryptography Standardization&quot; article spells out both numbers verbatim [@wp-nist-pqc]. The field narrowed to 26 algorithms in Round 2 (January 2019), then to 7 finalists plus 8 alternates in Round 3 (July 2020). NIST IR 8413 (July 2022) is the canonical status report on Round 3 [@nist-ir-8413].&lt;/p&gt;
&lt;p&gt;On 5 July 2022, NIST announced the first four standardisation selections: CRYSTALS-Kyber for key encapsulation, plus CRYSTALS-Dilithium, FALCON, and SPHINCS+ for signatures [@nist-pqc-selection-2022]. Three were lattice schemes; one (SPHINCS+) was hash-based. The same announcement moved Classic McEliece, BIKE, HQC, and SIKE to a fourth round for further evaluation. Twenty-five days later, the Castryck-Decru attack retired SIKE. NIST IR 8545 documents the eventual fourth-round selection of HQC (announced 7 March 2025) over BIKE, with Classic McEliece left as a candidate for niche-use standardisation due to its key size [@nist-hqc-news].&lt;/p&gt;

gantt
    dateFormat YYYY
    axisFormat %Y
    section Algorithm research
    Diffie-Hellman           :milestone, dh, 1976, 0
    Shor&apos;s algorithm         :milestone, shor, 1994, 0
    NTRU                     :milestone, ntru, 1996, 0
    Regev LWE                :milestone, lwe, 2005, 0
    Ring-LWE (LPR)           :milestone, rlwe, 2010, 0
    Module-LWE               :milestone, mlwe, 2012, 0
    BCNS / Frodo / NewHope   :milestone, bcns, 2014, 0
    section NIST process
    PQC call announced       :milestone, call, 2016, 0
    Round 1 (69 candidates)  :milestone, r1, 2017, 0
    Round 2 (26 candidates)  :milestone, r2, 2019, 0
    Round 3 finalists        :milestone, r3, 2020, 0
    Selections + Round 4     :milestone, sel, 2022, 0
    Rainbow + SIKE broken    :milestone, brk, 2022, 0
    FIPS 203 / 204 / 205     :milestone, fips, 2024, 0
    HQC selected             :milestone, hqc, 2025, 0
    section Windows shipping
    SymCrypt v103.5.0 ML-KEM :milestone, sc1, 2024, 0
    Insider Canary CNG PQ    :milestone, can, 2025, 0
    .NET 10 GA               :milestone, dn, 2025, 0
    Schannel X25519MLKEM768  :milestone, sch, 2026, 0
    TPM 2.0 v1.85 PQC        :milestone, tpm, 2026, 0
&lt;h3&gt;Generation 4 (2023-2024): standardisation&lt;/h3&gt;
&lt;p&gt;The draft FIPS standards published in August 2023; the final versions landed on 13 August 2024, when the Secretary of Commerce approved FIPS 203, FIPS 204, and FIPS 205 [@nist-fips-approved-news]. Names changed in the transition: CRYSTALS-Kyber [@crystals-kyber-paper] became Module-Lattice-Based Key-Encapsulation Mechanism (ML-KEM); CRYSTALS-Dilithium became Module-Lattice-Based Digital Signature Algorithm (ML-DSA); SPHINCS+ [@sphincsplus-framework] became Stateless Hash-Based Digital Signature Algorithm (SLH-DSA). The renaming was deliberate; NIST wanted standard names that described the construction rather than the project. Falcon&apos;s standardisation slipped to FIPS 206 in draft, principally because the floating-point Gaussian sampler required for Falcon&apos;s compact signatures is unusually hard to make both fast and constant-time [@nist-pqc-dig-sig].&lt;/p&gt;
&lt;h3&gt;Generation 5 (2024-2026): shipping on Windows&lt;/h3&gt;
&lt;p&gt;SymCrypt v103.5.0 added ML-KEM &quot;per final FIPS 203&quot; along with XMSS and XMSS^MT [@symcrypt-changelog]. Subsequent versions added LMS (v103.6.0), ML-DSA (v103.7.0), FIPS-approved-services indicator (v103.8.0), ML-DSA External-Mu sign/verify (v103.9.0), FIPS CAST plus ML-KEM/ML-DSA keygen pairwise consistency tests (v103.9.1), and Composite ML-KEM (v103.11.0). The Windows Insider Canary channel exposed the CNG identifiers in May 2025 [@ms-pqc-windows-insider]. .NET 10 (GA November 2025) shipped managed types &lt;code&gt;System.Security.Cryptography.MLKem&lt;/code&gt;, &lt;code&gt;MLKemCng&lt;/code&gt;, &lt;code&gt;MLDsa&lt;/code&gt;, &lt;code&gt;MLDsaCng&lt;/code&gt; [@dotnet-10-launch, @dotnet-mlkem, @dotnet-mlkemcng]. Schannel hybrid TLS 1.3 X25519MLKEM768 reached Server 2025 and 24H2 in preview behind Group Policy in early 2026.&lt;/p&gt;
&lt;p&gt;The competition is over. The standards are published. The SymCrypt versions are shipping. We have arrived at the moment where the algorithm internals matter -- because every Windows engineer now writes code against &lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt;, and code that uses an algorithm should know how it works.&lt;/p&gt;
&lt;h2&gt;5. The Breakthrough -- ML-KEM, ML-DSA, SLH-DSA at Engineer Depth&lt;/h2&gt;
&lt;p&gt;Three FIPS standards. Three algorithms. Three Windows API surfaces. Each rests on a different hardness assumption. Each has its own parameter zoo, key sizes, and side-channel surface. This section walks all three at the level a Windows engineer needs to make procurement, audit, and migration decisions.&lt;/p&gt;
&lt;h3&gt;5.1 ML-KEM (FIPS 203) -- the default KEM&lt;/h3&gt;
&lt;p&gt;ML-KEM is the only NIST-finalised key-encapsulation mechanism. It is the encryption primitive of the post-quantum era on Windows. The algebra is Module-LWE / Module-LWR over $R_q = \mathbb{Z}_q[X]/(X^{256} + 1)$ with $q = 3329$ -- a 12-bit prime chosen to make NTT arithmetic fast on 16-bit and 32-bit lanes [@fips-203-pdf]. The base ring has degree 256; the module rank $k$ selects the parameter set.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter set&lt;/th&gt;
&lt;th&gt;$k$&lt;/th&gt;
&lt;th&gt;NIST category&lt;/th&gt;
&lt;th&gt;Encapsulation key (bytes)&lt;/th&gt;
&lt;th&gt;Ciphertext (bytes)&lt;/th&gt;
&lt;th&gt;Shared secret (bytes)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;ML-KEM-512&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1 (AES-128 equivalent)&lt;/td&gt;
&lt;td&gt;800&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-KEM-768&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3 (AES-192 equivalent)&lt;/td&gt;
&lt;td&gt;1184&lt;/td&gt;
&lt;td&gt;1088&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-KEM-1024&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5 (AES-256 equivalent)&lt;/td&gt;
&lt;td&gt;1568&lt;/td&gt;
&lt;td&gt;1568&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The byte counts in the table are verbatim from the FIPS 203 standard [@fips-203-pdf, @wp-kyber]. Cloudflare&apos;s October 2022 deployment and Schannel&apos;s X25519MLKEM768 both target ML-KEM-768 specifically -- the category-3 sweet spot that survives even an aggressive cryptanalytic improvement against Module-LWE [@cloudflare-pq-for-all, @draft-tls-ecdhe-mlkem]. Apple&apos;s PQ3 splits its parameter selection: ML-KEM-1024 for the initial key exchange and Kyber-768 for the ongoing asymmetric ratchet [@apple-imessage-pq3]. OpenSSH 9.0+ deployed a different post-quantum primitive entirely -- Streamlined NTRU Prime in &lt;code&gt;sntrup761x25519-sha512&lt;/code&gt; [@openssh-9-0] -- and OpenSSH 9.9 (released 19 September 2024) added the ML-KEM-768-based group &lt;code&gt;mlkem768x25519-sha256&lt;/code&gt; available by default alongside it [@openssh-9-9].&lt;/p&gt;

A generic construction that converts an IND-CPA-secure public-key encryption scheme into an IND-CCA2-secure key-encapsulation mechanism. The transform re-encrypts the plaintext during decapsulation and verifies the resulting ciphertext bit-for-bit; any mismatch causes decapsulation to return an implicit-rejection pseudorandom value rather than the real shared secret. ML-KEM wraps an IND-CPA-secure scheme called K-PKE with the FO transform; the FO wrapper is what makes ML-KEM safe to use with long-term keys [@fips-203-pdf].
&lt;p&gt;ML-KEM has three operations: &lt;code&gt;KeyGen&lt;/code&gt; produces $(ek, dk)$; &lt;code&gt;Encaps(ek)&lt;/code&gt; produces $(K, c)$ where $K$ is the 32-byte shared secret and $c$ is the ciphertext; &lt;code&gt;Decaps(dk, c)&lt;/code&gt; recomputes $K$. The CNG surface mirrors this exactly. The canonical Microsoft idiom (from Microsoft Learn&apos;s CNG ML-KEM examples, currently marked prerelease) is &lt;code&gt;BCryptGenerateKeyPair&lt;/code&gt; with the pseudo-handle &lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt;, followed by &lt;code&gt;BCryptSetProperty&lt;/code&gt; setting &lt;code&gt;BCRYPT_PARAMETER_SET_NAME&lt;/code&gt; to &lt;code&gt;BCRYPT_MLKEM_PARAMETER_SET_768&lt;/code&gt;, followed by &lt;code&gt;BCryptFinalizeKeyPair&lt;/code&gt;, followed by &lt;code&gt;BCryptExportKey&lt;/code&gt; to extract the encapsulation key as a &lt;code&gt;BCRYPT_MLKEM_ENCAPSULATION_BLOB&lt;/code&gt; [@cng-mlkem-examples]. The new verbs &lt;code&gt;BCryptEncapsulate&lt;/code&gt; and &lt;code&gt;BCryptDecapsulate&lt;/code&gt; complete the picture; neither existed in CNG before the ML-KEM surface was added.&lt;/p&gt;

sequenceDiagram
    participant C as Client (Windows / Schannel)
    participant S as Server (Cloudflare / IIS)
    C-&amp;gt;&amp;gt;S: ClientHello key_share = X25519 (32B) || ML-KEM-768 ek (1184B)
    Note over S: Generates X25519 keypair
    Note over S: Computes ML-KEM Encaps(ek)
    Note over S: Yields ct and K_pq
    S-&amp;gt;&amp;gt;C: ServerHello key_share = X25519 (32B) || ML-KEM-768 ct (1088B)
    Note over C: Derives ECDH shared secret K_ecdh
    Note over C: Computes ML-KEM Decaps for K_pq
    Note over C,S: HKDF-Extract IKM = K_ecdh concat K_pq
    Note over C,S: Yields TLS 1.3 traffic secrets
&lt;p&gt;The internal construction of ML-KEM combines an IND-CPA-secure public-key encryption scheme called K-PKE with the Fujisaki-Okamoto-Hofheinz transform to produce an IND-CCA2 KEM. K-PKE is a Regev-style encryption with module structure; the encryption is illustrative-grade simple.&lt;/p&gt;
&lt;p&gt;{&lt;code&gt;// Illustrative ML-KEM K-PKE encryption. // The FIPS 203 standard is the normative source for byte-exact operations. // q = 3329, n = 256, ring R_q = Z_q[X] / (X^n + 1), module rank k in {2, 3, 4}. function kpkeEncrypt(ek: PublicKey, message: number[], seed: Uint8Array) {   const { A, t, k } = ek;           // A is k x k matrix in R_q, t is k-vector in R_q   const r  = sampleSmallCBD(seed, k);     // r:  k-vector, centred binomial noise   const e1 = sampleSmallCBD(seed, k);     // e1: k-vector, fresh noise   const e2 = sampleSmallSingle(seed);     // e2: scalar polynomial, fresh noise   const u = ringMatVecMul(transpose(A), r);   addInPlace(u, e1);                       // u = A^T r + e1   const v = ringDot(t, r);                 // v = t . r   addInPlace(v, e2);                       // v = t.r + e2   const mEncoded = encodeMessage(message); // 256-bit message -&amp;gt; R_q element   addInPlace(v, mEncoded);                 // v += Encode(message)   return { u, v };                          // ciphertext (u in R_q^k, v in R_q) }&lt;/code&gt;}&lt;/p&gt;
&lt;p&gt;The IND-CCA2 wrapper that becomes ML-KEM proper is the FO transform: hash the message and randomness into the encapsulation, then re-encrypt during decapsulation and reject if the ciphertext does not match. Decapsulation on a tampered ciphertext returns a pseudorandom shared secret derived from the secret key -- implicit rejection -- rather than an error code that an attacker could observe. This is what gives ML-KEM CCA2 security suitable for static keys in TLS, X.509, and CNG.&lt;/p&gt;
&lt;h3&gt;5.2 ML-DSA (FIPS 204) -- the default lattice signature&lt;/h3&gt;
&lt;p&gt;ML-DSA is the general-purpose lattice signature scheme. Same base ring of degree 256 as ML-KEM, but with a &lt;em&gt;different&lt;/em&gt; prime: $q = 8380417$, a 23-bit prime [@fips-204-pdf]. The disparity is intentional; ML-KEM and ML-DSA do not share keys, so their NTT parameter choices are independently optimised. The construction is Fiat-Shamir-with-aborts over Module-LWE and Module-SIS.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter set&lt;/th&gt;
&lt;th&gt;NIST category&lt;/th&gt;
&lt;th&gt;Public key (bytes)&lt;/th&gt;
&lt;th&gt;Signature (bytes)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;ML-DSA-44&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1312&lt;/td&gt;
&lt;td&gt;2420&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-DSA-65&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1952&lt;/td&gt;
&lt;td&gt;3293&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-DSA-87&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2592&lt;/td&gt;
&lt;td&gt;4595&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Numbers verbatim from FIPS 204 [@fips-204-pdf]. CNSA 2.0 selects ML-DSA-87 specifically as the general-signature algorithm for U.S. National Security Systems [@cnsa20-csa].&lt;/p&gt;

A signature construction in which the prover commits to a masking value, hashes the message and commitment to derive a challenge, computes a response that depends on the secret and the challenge, and *aborts and retries* if the response would leak the secret. The &quot;abort&quot; probability is bounded so signing completes in a small constant expected number of restarts. The technique, due to Lyubashevsky, is the foundation of ML-DSA&apos;s security argument; the rejection-sampling loop is also the source of a measurable timing variance that constant-time implementations must handle carefully [@fips-204-pdf].

flowchart TD
    Start[&quot;Begin sign(message, sk)&quot;] --&amp;gt; Sample[&quot;Sample masking vector y (small ball)&quot;]
    Sample --&amp;gt; Commit[&quot;Compute w = Ay in R_q&quot;]
    Commit --&amp;gt; Hash[&quot;c = H(message || HighBits(w))&quot;]
    Hash --&amp;gt; Resp[&quot;Compute response z = y + c*s_1&quot;]
    Resp --&amp;gt; Check{&quot;||z||_inf &amp;lt; bound?&lt;br /&gt;||LowBits(w - c*s_2)||_inf &amp;lt; bound?&quot;}
    Check --&amp;gt;|no| Sample
    Check --&amp;gt;|yes| Out[&quot;Return signature (z, c, h)&quot;]
&lt;p&gt;ML-DSA-87&apos;s 4595-byte signature is the bottom-of-stack constraint that drives every TPM and Pluton roadmap [@fips-204-pdf]. The default TPM 2.0 command and response buffers, fixed by historical compatibility decisions, are 4096 bytes. ML-DSA-65&apos;s 3293-byte signature fits; ML-DSA-87&apos;s 4595-byte signature does not. The TCG TPM 2.0 Library Specification v1.85 (March 2026) introduces a streaming Sign/Verify family and ML-KEM &lt;code&gt;Encapsulate&lt;/code&gt;/&lt;code&gt;Decapsulate&lt;/code&gt; opcodes that resolve the overflow; the full opcode inventory and the new &lt;code&gt;TPM2B_KEM_CIPHERTEXT&lt;/code&gt; / &lt;code&gt;TPM2B_SHARED_SECRET&lt;/code&gt; / &lt;code&gt;TPM_ST_MESSAGE_VERIFIED&lt;/code&gt; structures are catalogued in Section 9.1 [@wolfssl-wolftpm-v185]. Until v1.85 chips ship in retail volume, ML-DSA-87 cannot live on a commodity TPM. Cross-reference the &lt;a href=&quot;https://paragmali.com/blog/the-tpm-in-windows-one-primitive-twenty-five-years-and-the-c/&quot; rel=&quot;noopener&quot;&gt;TPM&lt;/a&gt; and &lt;a href=&quot;https://paragmali.com/blog/pluton-a-tpm-on-silicon-microsoft-can-patch/&quot; rel=&quot;noopener&quot;&gt;Pluton&lt;/a&gt; sibling articles for the silicon-side mechanics.Pluton&apos;s firmware-update agility -- the firmware ships through the existing Microsoft Update channel -- is the reason Pluton can move on PQ adoption faster than discrete TPM 2.0 chips, whose firmware updates depend on each TPM vendor&apos;s release cadence. The cross-reference to the Pluton sibling article in this series spells out the firmware-update mechanism in detail [@ms-quantum-safe-blog].&lt;/p&gt;
&lt;p&gt;The CNG surface mirrors ML-KEM&apos;s idiom with the signature primitives. &lt;code&gt;BCryptOpenAlgorithmProvider&lt;/code&gt; with &lt;code&gt;BCRYPT_MLDSA_ALGORITHM = L&quot;ML-DSA&quot;&lt;/code&gt; and &lt;code&gt;MS_PRIMITIVE_PROVIDER&lt;/code&gt; returns a handle; &lt;code&gt;BCryptSetProperty&lt;/code&gt; selects &lt;code&gt;BCRYPT_MLDSA_PARAMETER_SET_44&lt;/code&gt;, &lt;code&gt;_65&lt;/code&gt;, or &lt;code&gt;_87&lt;/code&gt;; &lt;code&gt;BCryptGenerateKeyPair&lt;/code&gt; plus &lt;code&gt;BCryptFinalizeKeyPair&lt;/code&gt; produces the keypair; key blobs are &lt;code&gt;BCRYPT_PQDSA_PUBLIC_KEY_BLOB&lt;/code&gt; and &lt;code&gt;BCRYPT_PQDSA_PRIVATE_KEY_BLOB&lt;/code&gt;; signing and verification go through &lt;code&gt;BCryptSignHash&lt;/code&gt; and &lt;code&gt;BCryptVerifySignature&lt;/code&gt; with a &lt;code&gt;BCRYPT_PQDSA_PADDING_INFO&lt;/code&gt; struct that selects pure-mode or pre-hash-mode (External-Mu, per FIPS 204&apos;s HashML-DSA variants) and carries an optional context string [@cng-mldsa-examples].&lt;/p&gt;
&lt;p&gt;{&lt;code&gt;// Illustrative ML-DSA signing. // Constants gamma1, gamma2, beta are parameter-set dependent. function mlDsaSign(message: Uint8Array, sk: SecretKey): Signature {   const { A, s1, s2, t0 } = sk;   let attempt = 0;   while (attempt &amp;lt; 1000) {     attempt++;     const y  = sampleMaskingVector(gamma1);     // y in R_q^l, ||y||_inf &amp;lt; gamma1     const w  = ringMatVecMul(A, y);              // w = A*y in R_q^k     const w1 = highBits(w, 2 * gamma2);     const c  = hashToChallenge(message, w1);     // c in B_tau     const z  = addVec(y, scalarMul(c, s1));      // z = y + c*s1     if (infNorm(z) &amp;gt;= gamma1 - beta) continue;   // reject if response too large     const r0 = lowBits(subVec(w, scalarMul(c, s2)), 2 * gamma2);     if (infNorm(r0) &amp;gt;= gamma2 - beta) continue;  // reject if low bits leak     return { z, c, h: makeHint(t0, c, w) };   }   throw new Error(&quot;ML-DSA signing exceeded attempt budget (statistically improbable)&quot;); }&lt;/code&gt;}&lt;/p&gt;
&lt;p&gt;ML-DSA sign time on x86-64 is in the low single-digit milliseconds; verify time is in the hundreds of microseconds. The rejection-sampling loop creates a measurable variance in sign-time -- a side channel that secret-key recovery exploits if the loop count, branch, or memory-access pattern leak.&lt;/p&gt;
&lt;h3&gt;5.3 SLH-DSA (FIPS 205) -- the conservative hash-based signature&lt;/h3&gt;
&lt;p&gt;SLH-DSA&apos;s security rests on hash-function security alone. No lattice. No code. No multivariate. No isogeny. Just preimage resistance and collision resistance of an underlying hash function (SHA-2 or SHAKE). If every algebraic post-quantum assumption breaks tomorrow, hash-based signatures still hold. The cost is signature size and signing time [@fips-205-pdf].&lt;/p&gt;
&lt;p&gt;The construction is a hypertree -- a tree of XMSS subtrees, with WOTS+ (Winternitz One-Time Signature Plus) leaves at each subtree level, and FORS (Forest of Random Subsets) few-time signatures at the bottom layer signing the actual message. The hypertree is sampled fresh per signature via a pseudorandom function of the message, which is what makes SPHINCS+ -&amp;gt; SLH-DSA stateless. Unlike LMS or XMSS, which require the signer to track a counter (because re-using a one-time key reveals the secret), SLH-DSA derives the leaf address from the message hash and a per-signature randomness; no signer state survives between signatures.&lt;/p&gt;

A one-time signature scheme. The signer publishes a public key consisting of hash-chain endpoints; the private key is the chain starts. Signing reveals intermediate chain values that depend on the message digest. A WOTS+ key signs exactly one message; signing a second message with the same key reveals enough chain values to forge any signature. WOTS+ is the leaf primitive of XMSS and SLH-DSA [@fips-205-pdf].

A few-time signature scheme built from $k$ independent hash trees of depth $t$. To sign a message, the signer hashes the message to obtain $k$ leaf indices and reveals the leaf preimage plus authentication path in each tree. Signing many messages with the same FORS key eventually reveals enough leaves to forge, but the few-times threshold is high enough to be tolerable when FORS is the bottom layer of an SLH-DSA hypertree whose root is signed by the layer above [@fips-205-pdf].

flowchart TD
    Root[&quot;SLH-DSA public key = root of top XMSS tree (32-64 bytes)&quot;]
    Root --&amp;gt; T1[&quot;Top XMSS subtree (WOTS+ leaves)&quot;]
    T1 --&amp;gt; T2[&quot;Middle XMSS subtrees (WOTS+ leaves)&quot;]
    T2 --&amp;gt; T3[&quot;More XMSS layers (parameter d controls depth)&quot;]
    T3 --&amp;gt; Bot[&quot;Bottom XMSS subtree&quot;]
    Bot --&amp;gt; FORS[&quot;FORS forest (k trees of depth t)&quot;]
    FORS --&amp;gt; Msg[&quot;Message digest derived from per-signature randomness&quot;]
&lt;p&gt;Twelve parameter sets ship in FIPS 205: every combination of &lt;code&gt;{SHA2, SHAKE}&lt;/code&gt; × &lt;code&gt;{128s, 128f, 192s, 192f, 256s, 256f}&lt;/code&gt; where &lt;code&gt;s&lt;/code&gt; is &quot;small signature, slow signing&quot; and &lt;code&gt;f&lt;/code&gt; is &quot;fast signing, larger signature&quot; [@fips-205-pdf]. Public keys are 32-64 bytes; signatures range from 7,856 bytes (SLH-DSA-SHA2-128s) to 49,856 bytes (SLH-DSA-SHA2-256f). Signing time ranges from ~10 ms (SLH-DSA-SHA2-128f) to several hundred milliseconds at the high end. The use case is &lt;em&gt;code signing&lt;/em&gt;: sign once, verify a billion times. CNG plans &lt;code&gt;BCRYPT_SLHDSA_ALGORITHM&lt;/code&gt; with the same &lt;code&gt;BCRYPT_PQDSA_KEY_BLOB&lt;/code&gt; and &lt;code&gt;BCRYPT_PQDSA_PADDING_INFO&lt;/code&gt; plumbing as ML-DSA [@cng-algorithm-ids].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; ML-KEM is the only NIST-finalised KEM. ML-DSA is the general-purpose lattice signature. SLH-DSA is the conservative hash-based fallback. They are not interchangeable; an engineer picks one (or all three) per use case. Hybrid TLS key agreement uses ML-KEM-768; X.509 end-entity signatures use ML-DSA-65 or ML-DSA-87; long-lived code signing where signature size is tolerable uses SLH-DSA; firmware signing with build-counter discipline uses LMS or XMSS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All three algorithms are FIPS-standardised. All three have CNG identifiers in Insider Canary builds. But until SymCrypt ships them, until Schannel negotiates them, until AD CS issues certificates that carry them, none of this exists for the Windows engineer in production. So what does Microsoft actually ship in May 2026?&lt;/p&gt;
&lt;h2&gt;6. State of the Art -- What Windows Ships in May 2026&lt;/h2&gt;
&lt;p&gt;Algorithms are not products. Microsoft ships SymCrypt, CNG, NCrypt, Schannel, .NET, AD CS, CertEnroll, and &lt;a href=&quot;https://paragmali.com/blog/windows-app-identity-33-year-reinvention/&quot; rel=&quot;noopener&quot;&gt;Authenticode&lt;/a&gt; -- and post-quantum cryptography arrives in each surface on its own clock.&lt;/p&gt;
&lt;h3&gt;SymCrypt -- the FIPS-validated foundation&lt;/h3&gt;
&lt;p&gt;SymCrypt is Microsoft&apos;s primary cryptographic library. The repository description states it directly: &quot;SymCrypt is the core cryptographic function library currently used by Windows ... started in late 2006 with the first sources committed in Feb 2007 ... Since the 1703 release of Windows 10, SymCrypt has been the primary crypto library for all algorithms in Windows&quot; [@symcrypt-repo]. Microsoft open-sourced SymCrypt in March 2019 [@symcrypt-repo]. It is the FIPS 140-validated module that backs CNG; if CNG ships a post-quantum algorithm on Windows, SymCrypt is the implementation underneath.&lt;/p&gt;

Microsoft&apos;s open-source cryptographic library, used by Windows, Azure Linux, Xbox, and other Microsoft platforms. SymCrypt has been Windows&apos;s primary cryptographic library since Windows 10 1703 (April 2017); its FIPS 140-validated module is the implementation backing CNG (the Win32 API surface) and the NCrypt KSP infrastructure (the key-storage-provider surface). SymCrypt is currently written predominantly in cross-platform C, with an in-progress Rust rewrite for memory-safety reasons [@symcrypt-repo, @ms-research-symcrypt-rust].
&lt;p&gt;The SymCrypt release history through May 2026 is verbatim from the public CHANGELOG [@symcrypt-changelog].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Post-quantum change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;103.5.0&lt;/td&gt;
&lt;td&gt;Add ML-KEM per final FIPS 203; add XMSS / XMSS^MT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103.6.0&lt;/td&gt;
&lt;td&gt;Add LMS implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103.7.0&lt;/td&gt;
&lt;td&gt;Add ML-DSA implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103.8.0&lt;/td&gt;
&lt;td&gt;Add FIPS approved-services indicator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103.9.0&lt;/td&gt;
&lt;td&gt;Add ML-DSA Sign / Verify with External Mu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103.9.1&lt;/td&gt;
&lt;td&gt;Add FIPS CAST for ML-DSA, plus ML-KEM and ML-DSA keygen pairwise-consistency tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103.11.0&lt;/td&gt;
&lt;td&gt;Add Composite ML-KEM implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The SymCrypt releases page lists the binary artefacts at each version for Windows AMD64/ARM64, generic Linux AMD64/ARM64, and OpenEnclave AMD64 [@symcrypt-releases]. Microsoft has also begun rewriting SymCrypt in Rust; the Microsoft Research blog post describes the rationale (memory safety in a TCB-grade library) and confirms the algorithm coverage includes &quot;AES-GCM, SHA, ECDSA, and the more recent post-quantum algorithms ML-KEM and ML-DSA&quot; [@ms-research-symcrypt-rust].&lt;/p&gt;
&lt;h3&gt;CNG, NCrypt, and .NET 10&lt;/h3&gt;
&lt;p&gt;The Windows Insider Canary channel introduced post-quantum CNG identifiers in May 2025 [@ms-pqc-windows-insider]. The new pseudo-handle &lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt;, the algorithm-name strings &lt;code&gt;BCRYPT_MLKEM_ALGORITHM = L&quot;ML-KEM&quot;&lt;/code&gt; and &lt;code&gt;BCRYPT_MLDSA_ALGORITHM = L&quot;ML-DSA&quot;&lt;/code&gt;, the prerelease &lt;code&gt;BCRYPT_SLHDSA_ALGORITHM&lt;/code&gt;, and the existing &lt;code&gt;BCRYPT_LMS_ALGORITHM&lt;/code&gt; are documented on the CNG Algorithm Identifiers page [@cng-algorithm-ids]. NCrypt KSPs expose the same algorithm names; an application that previously called &lt;code&gt;NCryptCreatePersistedKey&lt;/code&gt; against an RSA KSP can do the equivalent against an ML-KEM KSP with no plumbing changes beyond the algorithm identifier and parameter set.&lt;/p&gt;
&lt;p&gt;.NET 10 (GA November 2025) exposes the managed surface [@dotnet-10-launch]. &lt;code&gt;System.Security.Cryptography.MLKem&lt;/code&gt; is an abstract base class with &lt;code&gt;KeyGen&lt;/code&gt;, &lt;code&gt;Encapsulate&lt;/code&gt;, &lt;code&gt;Decapsulate&lt;/code&gt;, &lt;code&gt;ExportEncapsulationKey&lt;/code&gt;, and &lt;code&gt;ImportEncapsulationKey&lt;/code&gt; instance methods [@dotnet-mlkem]. &lt;code&gt;MLKemCng&lt;/code&gt; is the CNG-backed concrete subclass that forwards to SymCrypt via CNG [@dotnet-mlkemcng]. Equivalent &lt;code&gt;MLDsa&lt;/code&gt; / &lt;code&gt;MLDsaCng&lt;/code&gt; and &lt;code&gt;SlhDsa&lt;/code&gt; / &lt;code&gt;SlhDsaCng&lt;/code&gt; pairs cover the signature primitives. The &lt;code&gt;*Cng&lt;/code&gt; subclasses are sealed; the abstract base classes are subclassable for non-CNG implementations.&lt;/p&gt;
&lt;h3&gt;Schannel hybrid TLS 1.3&lt;/h3&gt;
&lt;p&gt;Schannel is the Windows TLS stack. The hybrid TLS 1.3 Supported Groups are defined by IETF &lt;code&gt;draft-ietf-tls-ecdhe-mlkem-04&lt;/code&gt; (8 February 2026) [@draft-tls-ecdhe-mlkem]:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Codepoint&lt;/th&gt;
&lt;th&gt;Construction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X25519MLKEM768&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0x11EC&lt;/td&gt;
&lt;td&gt;RFC 7748 X25519 plus ML-KEM-768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SecP256r1MLKEM768&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0x11EB&lt;/td&gt;
&lt;td&gt;NIST P-256 plus ML-KEM-768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SecP384r1MLKEM1024&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0x11ED&lt;/td&gt;
&lt;td&gt;NIST P-384 plus ML-KEM-1024&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The IANA TLS Parameters registry lists all three (registry last updated 2026-04-29) [@iana-tls-parameters]. Schannel preview on 24H2 and Server 2025 gates these behind Group Policy in early 2026; default-on is the May 2026 -&amp;gt; November 2026 milestone per Microsoft&apos;s Quantum-Safe Security blog [@ms-quantum-safe-blog]. The actual TLS key schedule combines the two shared secrets with a concatenation combiner: $\text{HKDF-Extract}(\text{salt} = 0, \text{IKM} = K_{\text{ecdh}} | K_{\text{pq}})$, per &lt;code&gt;draft-ietf-tls-hybrid-design-16&lt;/code&gt; [@draft-tls-hybrid]. The combiner is correct as long as either component is unbroken; an adversary breaking only ECDH cannot recover the session key, nor can one who breaks only ML-KEM.&lt;/p&gt;
&lt;h3&gt;AD CS, CertEnroll, and Azure Key Vault&lt;/h3&gt;
&lt;p&gt;The X.509 side of the migration lags TLS by a year. Active Directory Certificate Services supports ML-DSA certificate templates via the CertEnroll API, conditional on a CSP or KSP exposing &lt;code&gt;BCRYPT_MLDSA_ALGORITHM&lt;/code&gt;. The practical migration mechanism is &lt;em&gt;composite&lt;/em&gt; signatures per &lt;code&gt;draft-ietf-lamps-pq-composite-sigs-19&lt;/code&gt; (21 April 2026), which combines ML-DSA with RSA-PKCS#1-v1.5, RSA-PSS, ECDSA, Ed25519, or Ed448 in a single &lt;code&gt;SubjectPublicKeyInfo&lt;/code&gt; and requires both components to verify [@draft-lamps-composite]. Downlevel verifiers that do not recognise the composite OID can still validate the inner classical chain; uplevel verifiers validate both. Pure post-quantum X.509 chains are in preview for closed pilots, not in general use. Azure Key Vault&apos;s managed-HSM exposes post-quantum keys in preview for Q1 2026.&lt;/p&gt;

flowchart TD
    ISV[&quot;ISV applications (browsers, services, SDKs)&quot;]
    Schannel[&quot;Schannel (TLS 1.3 with X25519MLKEM768)&quot;]
    ADCS[&quot;AD CS / CertEnroll (ML-DSA, composite signatures)&quot;]
    DotNet[&quot;.NET 10 (MLKem, MLDsa, SlhDsa managed types)&quot;]
    KSP[&quot;NCrypt KSPs (Microsoft Software KSP, Pluton KSP, vendor KSPs)&quot;]
    CNG[&quot;CNG Win32 API (BCryptEncapsulate, BCryptSignHash, ...)&quot;]
    SymCrypt[&quot;SymCrypt (FIPS-validated, primary crypto library since Win10 1703)&quot;]
    HW[&quot;Hardware (CPU AES-NI, Pluton, TPM 2.0, IOMMU)&quot;]
    ISV --&amp;gt; Schannel
    ISV --&amp;gt; ADCS
    ISV --&amp;gt; DotNet
    Schannel --&amp;gt; CNG
    ADCS --&amp;gt; CNG
    DotNet --&amp;gt; CNG
    Schannel --&amp;gt; KSP
    KSP --&amp;gt; CNG
    CNG --&amp;gt; SymCrypt
    SymCrypt --&amp;gt; HW
&lt;h3&gt;What is NOT shipping in May 2026&lt;/h3&gt;
&lt;p&gt;The honest accounting matters. Several load-bearing Windows surfaces have no post-quantum path as of May 2026.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The post-quantum migration is partial. As of May 2026, none of the following has a published Microsoft post-quantum specification or shipping implementation: IKEv2 PQ key exchange; SMB hybrid (&lt;code&gt;X25519MLKEM768&lt;/code&gt; over SMB 3.1.1); RDP hybrid; BitLocker network unlock (still RSA-2048 + AES-256); Kerberos PKINIT (no PQ certificate path for the KDC bootstrap); Windows Hello attestation (TPM-bound RSA-2048 / ECDSA-P256). Authenticode signatures on drivers and binaries remain RSA-2048 + SHA-256 with no published PQ Authenticode specification. Premature migration of these surfaces is &lt;em&gt;worse&lt;/em&gt; than no migration, because there is no downlevel-compatible composite story for them. The discipline is: hybrid TLS first, composite X.509 chain second, firmware signing pilot third. Leave the rest alone until Microsoft publishes specifications [@ms-quantum-safe-blog].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;CNSA 2.0 -- the policy clock&lt;/h3&gt;
&lt;p&gt;CNSA 2.0 turns the technical timeline into an acquisition mandate. The four authoritative dates from the May 30, 2025 revision of the Cybersecurity Advisory [@cnsa20-csa]:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Milestone&lt;/th&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Acquisition preference for PQ in new National Security Systems&lt;/td&gt;
&lt;td&gt;January 1, 2027&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legacy algorithm phase-out begins&lt;/td&gt;
&lt;td&gt;December 31, 2030&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mandatory PQ adoption in National Security Systems&lt;/td&gt;
&lt;td&gt;December 31, 2031&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA / ECDSA disallowed in National Security Systems&lt;/td&gt;
&lt;td&gt;After 2035&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

The U.S. National Security Agency&apos;s Commercial National Security Algorithm Suite 2.0, announced September 7, 2022 [@nsa-cnsa-news]. CNSA 2.0 mandates post-quantum algorithms for U.S. National Security Systems by 2031 and disallows RSA / ECDSA after 2035. Specific algorithm selections (May 30, 2025 revision): ML-KEM-1024 for key establishment, ML-DSA-87 for general signing, LMS and XMSS for firmware signing, AES-256 for symmetric encryption, SHA-384 for hashing [@cnsa20-csa]. The CNSA 2.0 dates drive every U.S. vendor&apos;s PQC roadmap including Microsoft&apos;s.

A FIPS 140-3 validation is a Cryptographic Module Validation Program certificate that asserts a cryptographic module (a binary, with a specific version, a specific build, and a specific tested configuration) implements specific algorithms correctly and has been tested by an accredited lab. SymCrypt&apos;s FIPS validation is what makes CNG-backed cryptography acceptable for U.S. federal procurement; without validation, the same algorithm implemented in the same byte-exact code is not FIPS-validated. The cadence matters because algorithm-implementation cadence (new ML-DSA External-Mu support in v103.9.0) and module-validation cadence (a new CMVP certificate per validated build) are different clocks. SymCrypt v103.8.0 explicitly added a FIPS approved-services indicator [@symcrypt-changelog] -- the runtime hook by which an application can ask &quot;am I operating in FIPS-validated mode?&quot; and reject non-FIPS algorithms accordingly. CMVP queue times in 2026 are running 9-18 months, which means the published SymCrypt version is typically two or three versions ahead of the FIPS-validated version at any given moment.
&lt;p&gt;ML-KEM is the only NIST-finalised KEM. ML-DSA and SLH-DSA are the only NIST-finalised signature schemes. But the NIST portfolio still has Falcon in FIPS 206 draft, HQC for code-based diversification, LMS / XMSS for firmware -- and the IETF still has composite signatures and hybrid TLS layered on top. What else is shipping, and why?&lt;/p&gt;
&lt;h2&gt;7. Competing Approaches -- Inside the Lattice Lane and Outside It&lt;/h2&gt;
&lt;p&gt;ML-KEM is the only KEM in FIPS 203, but it is not the only KEM in the portfolio. Several other algorithms compete for adjacent niches, and the engineer who treats &quot;PQ&quot; as one thing misses the architectural choices that CNSA 2.0 and NIST actually make.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Falcon (FN-DSA, FIPS 206 draft).&lt;/strong&gt; NTRU-lattice signatures with fast Fourier sampling. Signature sizes range from 666 bytes (Falcon-512, category 1) to 1280 bytes (Falcon-1024, category 5) -- three to five times smaller than ML-DSA-65 at comparable security; the byte counts are verbatim from the Falcon Round-3 specification&apos;s recommended-parameters table [@falcon-spec]. The cost is Falcon&apos;s Gaussian sampler, which requires floating-point arithmetic and is notoriously hard to make constant-time. Microsoft has signalled support; SymCrypt has not shipped Falcon as of May 2026. FIPS 206 finalisation is the precondition; NIST&apos;s &lt;code&gt;pqc-dig-sig&lt;/code&gt; project page lists Falcon (renamed FN-DSA) as the standard whose finalisation has been pushed past initial timelines pending the constant-time sampler question [@nist-pqc-dig-sig].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HQC (Hamming Quasi-Cyclic).&lt;/strong&gt; NIST selected HQC as the fourth-round standardisation choice on 7 March 2025 [@nist-hqc-news]. HQC is code-based -- its security rests on the hardness of decoding random quasi-cyclic codes -- which is structurally unrelated to lattice cryptography. NIST IR 8545 documents the rationale: HQC offers diversification away from lattices in case future cryptanalysis makes Module-LWE less conservative than it now appears. HQC was chosen over BIKE; Classic McEliece remains a candidate but was not selected because of key size. NIST is expected to publish the HQC standard around 2027.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Classic McEliece.&lt;/strong&gt; ~1 MB public keys at NIST category 5; ~261 KB at category 1 [@mceliece-project]. Forty-eight years of cryptanalysis without a structural break. Not selected by NIST for general standardisation. Survives as a niche choice for long-term archival key wrapping, where the key transfer happens once and the ciphertext is small.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LMS, XMSS, XMSS^MT (stateful hash-based, NIST SP 800-208).&lt;/strong&gt; Already in SymCrypt. The NIST SP 800-208 specification names &quot;two algorithms ... stateful hash-based signature schemes: the Leighton-Micali Signature (LMS) system and the eXtended Merkle Signature Scheme (XMSS), along with their multi-tree variants (HSS and XMSS_MT)&quot; [@nist-sp-800-208]. CNSA 2.0 specifies LMS and XMSS for firmware signing: UEFI capsule signing, OEM driver signing, secure-boot dbx revocation entries [@cnsa20-csa]. The stateful-counter requirement -- the signer must track a build counter and never reuse a leaf index -- is acceptable in build pipelines that already track build numbers monotonically. SymCrypt v103.5.0 added XMSS and XMSS^MT; v103.6.0 added LMS [@symcrypt-changelog].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; CNSA 2.0 names stateful hash-based signatures for firmware signing and only firmware signing. The reason is operational. LMS and XMSS are state-leaking: signing twice with the same leaf index reveals the secret. A general-purpose signing surface (X.509 end-entity certificates, code signing for arbitrary developers, document signing) cannot guarantee state discipline. A firmware-build pipeline that issues build numbers monotonically and operates under hardware-security-module discipline can. The narrow scope is what makes LMS / XMSS safe to deploy now -- the stateless SLH-DSA story is the general-purpose alternative for everything that cannot guarantee counter discipline [@cnsa20-csa, @nist-sp-800-208].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Composite signatures.&lt;/strong&gt; &lt;code&gt;draft-ietf-lamps-pq-composite-sigs-19&lt;/code&gt; (21 April 2026) defines combinations of ML-DSA with each of RSA-PKCS#1-v1.5, RSA-PSS, ECDSA, Ed25519, and Ed448 [@draft-lamps-composite]. The X.509 SubjectPublicKeyInfo contains both component public keys; verification requires both component signatures to succeed; an attacker must break both algorithms. Composite is the &lt;strong&gt;load-bearing migration mechanism for 2026-2030&lt;/strong&gt;, because it is deployable against today&apos;s PKI. Downlevel verifiers ignore the composite OID and trust the inner classical chain; uplevel verifiers validate both.&lt;/p&gt;

A signature construction that combines two component signature algorithms -- one classical, one post-quantum -- such that both signatures must verify for the composite signature to validate. The composite public key is the concatenation of the two component public keys plus a composite-OID wrapper; the composite signature is the concatenation of the two component signatures. The construction provides &quot;either-component&quot; security: an adversary must break both algorithms to forge. Composite is the practical migration on-ramp for X.509 PKI during 2026-2030 [@draft-lamps-composite].
&lt;p&gt;&lt;strong&gt;Hybrid TLS X25519MLKEM768.&lt;/strong&gt; Already in production at internet scale. Cloudflare since October 2022; Apple iMessage PQ3 since February 2024; Signal PQXDH since September 2023 [@signal-pqxdh]; OpenSSH since version 9.0 (April 2022) via &lt;code&gt;sntrup761x25519-sha512&lt;/code&gt; (Streamlined NTRU Prime + X25519) [@openssh-9-0], with the ML-KEM-768-based group &lt;code&gt;mlkem768x25519-sha256&lt;/code&gt; added in OpenSSH 9.9 (September 2024) [@openssh-9-9]; Google Chrome; Microsoft Edge. Schannel preview on 24H2 and Server 2025 in early 2026 [@cloudflare-pq-for-all, @cloudflare-pq-2024, @apple-imessage-pq3].Apple&apos;s framing of PQ3 as &quot;Level 3&quot; is the policy-marketing achievement of the post-quantum era. Level 1 is no post-quantum; Level 2 is post-quantum key establishment for the &lt;em&gt;initial&lt;/em&gt; handshake; Level 3 is post-quantum key establishment for &lt;em&gt;both&lt;/em&gt; the initial handshake and ongoing message-key ratcheting. iMessage PQ3 reached Level 3 in February 2024 -- six months before ML-KEM was even FIPS-finalised. The companion symbolic-analysis PDF Apple commissioned confirms the parameter split: ML-KEM-1024 for the initial key exchange, Kyber-768 for the ongoing ratchet [@apple-imessage-pq3].&lt;/p&gt;

A key-exchange construction that combines a classical key-agreement algorithm (X25519, ECDH-P256, RSA) with a post-quantum KEM (ML-KEM-768) in such a way that the final shared secret depends on both components. An adversary who breaks either component but not both learns nothing. The most common combiner is HKDF-Extract over the concatenation of the two shared secrets, per `draft-ietf-tls-hybrid-design-16` [@draft-tls-hybrid]. Hybrid is the migration choice during the period when neither classical nor post-quantum primitives can be trusted standalone -- classical because of harvest-now-decrypt-later, post-quantum because of the narrower cryptanalytic margin (see Section 8).
&lt;p&gt;Compact portfolio comparison for the KEM side (HQC sizes are current Round-4 parameters from the HQC project specification [@hqc-project]):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Public key&lt;/th&gt;
&lt;th&gt;Ciphertext&lt;/th&gt;
&lt;th&gt;Hardness&lt;/th&gt;
&lt;th&gt;NIST status&lt;/th&gt;
&lt;th&gt;Microsoft adoption&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;ML-KEM-768&lt;/td&gt;
&lt;td&gt;1184 B&lt;/td&gt;
&lt;td&gt;1088 B&lt;/td&gt;
&lt;td&gt;Module-LWE&lt;/td&gt;
&lt;td&gt;FIPS 203&lt;/td&gt;
&lt;td&gt;SymCrypt 103.5.0; CNG; Schannel hybrid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HQC-1&lt;/td&gt;
&lt;td&gt;2241 B (cat 1)&lt;/td&gt;
&lt;td&gt;4433 B (cat 1)&lt;/td&gt;
&lt;td&gt;Quasi-cyclic codes&lt;/td&gt;
&lt;td&gt;Round-4 selected (March 2025)&lt;/td&gt;
&lt;td&gt;Not yet in SymCrypt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Classic McEliece&lt;/td&gt;
&lt;td&gt;~261 KB to ~1 MB&lt;/td&gt;
&lt;td&gt;~128 B to ~240 B&lt;/td&gt;
&lt;td&gt;Goppa codes&lt;/td&gt;
&lt;td&gt;Round-4 niche&lt;/td&gt;
&lt;td&gt;Not in SymCrypt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid X25519+MLKEM768&lt;/td&gt;
&lt;td&gt;1216 B&lt;/td&gt;
&lt;td&gt;1120 B&lt;/td&gt;
&lt;td&gt;X25519 OR Module-LWE&lt;/td&gt;
&lt;td&gt;TLS 1.3 IETF draft&lt;/td&gt;
&lt;td&gt;Schannel preview, default-on roadmap&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Signature side:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Public key&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Hardness&lt;/th&gt;
&lt;th&gt;NIST status&lt;/th&gt;
&lt;th&gt;Microsoft adoption&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;ML-DSA-65&lt;/td&gt;
&lt;td&gt;1952 B&lt;/td&gt;
&lt;td&gt;3293 B&lt;/td&gt;
&lt;td&gt;Module-LWE / Module-SIS&lt;/td&gt;
&lt;td&gt;FIPS 204&lt;/td&gt;
&lt;td&gt;SymCrypt 103.7.0; CNG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Falcon-512&lt;/td&gt;
&lt;td&gt;897 B&lt;/td&gt;
&lt;td&gt;666 B [@falcon-spec]&lt;/td&gt;
&lt;td&gt;NTRU lattices&lt;/td&gt;
&lt;td&gt;FIPS 206 draft&lt;/td&gt;
&lt;td&gt;Not in SymCrypt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SLH-DSA-SHA2-128f&lt;/td&gt;
&lt;td&gt;32 B&lt;/td&gt;
&lt;td&gt;17088 B&lt;/td&gt;
&lt;td&gt;SHA-2 collision resistance&lt;/td&gt;
&lt;td&gt;FIPS 205&lt;/td&gt;
&lt;td&gt;Planned &lt;code&gt;BCRYPT_SLHDSA_ALGORITHM&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LMS / HSS&lt;/td&gt;
&lt;td&gt;60 B&lt;/td&gt;
&lt;td&gt;4-50 KB&lt;/td&gt;
&lt;td&gt;Hash preimage&lt;/td&gt;
&lt;td&gt;NIST SP 800-208&lt;/td&gt;
&lt;td&gt;SymCrypt 103.6.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composite ML-DSA-65 + ECDSA-P256&lt;/td&gt;
&lt;td&gt;~2 KB&lt;/td&gt;
&lt;td&gt;~3.4 KB&lt;/td&gt;
&lt;td&gt;ML-DSA AND ECDSA&lt;/td&gt;
&lt;td&gt;LAMPS draft-19&lt;/td&gt;
&lt;td&gt;AD CS pilot path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The portfolio works as long as one of its families holds. But what if it doesn&apos;t? What does cryptography &lt;em&gt;not&lt;/em&gt; tell us about the future, and what are the structural limits of even the strongest post-quantum primitive?&lt;/p&gt;
&lt;h2&gt;8. Theoretical Limits -- What PQC Does and Does Not Solve&lt;/h2&gt;
&lt;p&gt;Post-quantum cryptography is not magic. It closes one specific channel of one specific threat model, and engineers who treat it as &quot;now we&apos;re quantum-safe&quot; miss the four limits the cryptographers themselves keep flagging.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. The cryptanalysis margin is narrower than for RSA or ECDH.&lt;/strong&gt; The best classical algorithm for solving Module-LWE at NIST parameter sizes (BKZ with sieving) runs in roughly $2^{0.292 n}$ operations, where $n$ is the lattice dimension; the best quantum variant runs in roughly $2^{0.257 n}$. That is a 12% exponent reduction -- not a Shor-style polynomial-time collapse. NIST parameter sizes carry a small but measurable margin to absorb future BKZ-with-sieving improvements. The hardness conjecture is stronger than RSA&apos;s (worst-case-to-average-case reduction), but the cryptanalytic frontier is thinner. Lattice cryptanalysis has improved continuously since Ajtai 1996; whether the asymptotic exponent further drops in the next decade is an open problem [@regev-2005, @langlois-stehle-modulelwe].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. The side-channel surface is larger.&lt;/strong&gt; ML-DSA&apos;s rejection-sampling loop is secret-correlated; Falcon&apos;s Gaussian sampler requires floating-point arithmetic; ML-KEM&apos;s polynomial operations can leak through cache-timing channels. The most visceral example is &lt;strong&gt;KyberSlash&lt;/strong&gt; (eprint 2024/1049, advisory GHSA-x5j2-g63m-f8g4), in which Bernstein and collaborators demonstrated that the &lt;em&gt;official Kyber reference implementation&lt;/em&gt; contained a secret-dependent division-timing leak that survived multiple rounds of NIST review and recovered secret keys in minutes on a Raspberry Pi 2 [@kyberslash-2024].KyberSlash is the most important data point in PQC implementation security. The leak was a one-line &lt;code&gt;/&lt;/code&gt; operator that compiled to a variable-time integer division on ARM and on older x86-64. The vulnerability survived years of formal NIST review, multiple academic implementations, and several vendor ports. Constant-time discipline is more fragile in PQ primitives than in classical primitives -- both because the algorithms are newer and because the ring arithmetic offers many more variable-time corners than the simpler scalar arithmetic of ECDH or RSA. The KyberSlash site, authored by Bernstein, documents specific implementations affected [@kyberslash-2024].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. The signed-binary harvest is not closed by PQ.&lt;/strong&gt; This is the article&apos;s third aha moment, and the one most readers miss. A 2026 Authenticode signature on a 2026 Windows driver uses RSA-2048 + SHA-256. In 2035, the verifier may no longer trust RSA-2048 -- but the binary has already been loaded by every machine that downloaded it. Authenticode is not a transport channel. There is no migration-window analogue of harvest-now-decrypt-later because the signature was already validated at load time. The threat model is &quot;an adversary in 2035 forges a &lt;em&gt;new&lt;/em&gt; signature on a &lt;em&gt;new&lt;/em&gt; binary,&quot; not &quot;an adversary in 2035 decrypts a 2026 conversation.&quot; The two threat models call for different migration disciplines.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Post-quantum cryptography closes the harvest-now-decrypt-later channel for transport-protected traffic (TLS, IPsec, SSH, iMessage). It does not close the signed-binary persistence channel; a 2035 quantum-forged signature on a 2035 driver is a &lt;em&gt;new&lt;/em&gt; attack, not a retroactive decryption of a 2026 signature. It does not close the algorithm-agility gap; CNG ships per-algorithm identifiers, not per-algorithm-class. Plan migration accordingly. Hybrid TLS first; composite X.509 chain second; firmware signing pilot third. Authenticode and PKINIT can wait for Microsoft&apos;s published specifications -- and premature migration in those surfaces is &lt;em&gt;worse&lt;/em&gt; than no migration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;4. The algorithm-agility problem persists.&lt;/strong&gt; Microsoft has shipped CNG identifiers &lt;em&gt;per algorithm&lt;/em&gt; (&lt;code&gt;BCRYPT_MLKEM_ALGORITHM&lt;/code&gt;, &lt;code&gt;BCRYPT_MLDSA_ALGORITHM&lt;/code&gt;) rather than per algorithm-class (a hypothetical &lt;code&gt;BCRYPT_PQ_KEM_ALGORITHM&lt;/code&gt; that selected the underlying primitive at runtime). The IETF treats algorithm agility as a load-bearing concern in &lt;code&gt;draft-ietf-pquip-pqc-engineers-14&lt;/code&gt; (26 August 2025), the IETF informational document on engineering PQC into existing protocol surfaces [@draft-pquip-engineers]. CNG does not yet treat it as load-bearing; the engineering consequences for the next migration are discussed in Section 9.4.&lt;/p&gt;

The property of a cryptographic protocol or library that lets the underlying algorithm change without changing the protocol or API surface. A protocol that names &quot;AES-256-GCM&quot; instead of &quot;an AEAD with at least 128-bit security&quot; has poor algorithm agility; replacing AES-256-GCM with ChaCha20-Poly1305 requires the entire protocol to be re-negotiated. CNG&apos;s `BCRYPT_MLKEM_ALGORITHM` is per-algorithm rather than per-algorithm-class; a future Round-5 KEM will require new CNG plumbing rather than a parameter change. The IETF `pqc-engineers` document treats algorithm agility as the load-bearing engineering concern for the post-2030 migration window [@draft-pquip-engineers].

A common confusion is the simultaneous truth that lattice cryptography has a *stronger* hardness argument than RSA (the worst-case-to-average-case reduction) and a *narrower* cryptanalytic margin (the 12% exponent gap). Both are true. The strength claim is structural: every average-case Module-LWE instance is hard if any worst-case lattice instance is hard. The narrowness claim is empirical: the best known algorithm is closer to a feasibility threshold than the best known algorithm for factoring or for elliptic-curve discrete log. The conservative McEliece line trades the strength claim (no analogous reduction) for an even wider empirical margin (no progress on Goppa-code decoding in 48 years). Engineers who treat &quot;stronger hardness&quot; and &quot;wider margin&quot; as synonyms get the post-quantum picture backwards. The honest framing: lattice is the deployable post-quantum, McEliece is the conservative fallback, and the portfolio exists because no one assumption carries everything.
&lt;p&gt;These four limits are not bugs; they are structural. But they are not the only open problems. What is the cryptographer&apos;s current research frontier, and where will the next migration begin?&lt;/p&gt;
&lt;h2&gt;9. Open Problems -- Where the Active Research Is&lt;/h2&gt;
&lt;p&gt;What does Microsoft, NIST, and the IETF still not know? Five open problems whose resolution will define the next decade of Windows cryptography.&lt;/p&gt;
&lt;h3&gt;9.1 TPM 2.0 and Pluton blob-size constraints&lt;/h3&gt;
&lt;p&gt;Default &lt;code&gt;MAX_COMMAND_SIZE&lt;/code&gt; and &lt;code&gt;MAX_RESPONSE_SIZE&lt;/code&gt; on TPM 2.0 are 4096 bytes. ML-DSA-87 signatures (4595 bytes) overflow the response buffer; ML-DSA-65 (3293 bytes) fits. NV memory budgets on commodity TPMs are tightly constrained, which means storing a single ML-DSA-87 keypair (2592-byte public key plus a multi-kilobyte private state) consumes a meaningful fraction of the available NV slot space [@wolfssl-wolftpm-v185, @fips-204-pdf]. The TCG TPM 2.0 Library Specification v1.85 (March 2026) introduces the streaming command family that resolves the buffer overflow; the cited wolfSSL secondary source enumerates the new commands verbatim as &lt;code&gt;TPM2_SignSequenceStart&lt;/code&gt; / &lt;code&gt;TPM2_VerifySequenceStart&lt;/code&gt;, &lt;code&gt;TPM2_SignSequenceComplete&lt;/code&gt; / &lt;code&gt;TPM2_VerifySequenceComplete&lt;/code&gt;, and &lt;code&gt;TPM2_SignDigest&lt;/code&gt; / &lt;code&gt;TPM2_VerifyDigestSignature&lt;/code&gt; for digest-mode operations, plus &lt;code&gt;TPM2_Encapsulate&lt;/code&gt; and &lt;code&gt;TPM2_Decapsulate&lt;/code&gt; for ML-KEM, with the new structures &lt;code&gt;TPM2B_KEM_CIPHERTEXT&lt;/code&gt;, &lt;code&gt;TPM2B_SHARED_SECRET&lt;/code&gt;, and &lt;code&gt;TPM_ST_MESSAGE_VERIFIED&lt;/code&gt; (the matching &lt;code&gt;*SequenceUpdate&lt;/code&gt; opcode is implied by analogy with the existing TPM 2.0 hash-sequence command family but is not enumerated in the available secondary source pending TCG primary access) [@wolfssl-wolftpm-v185]. Commodity v1.85-capable chips are entering early sampling in 2026; &lt;strong&gt;Pluton&apos;s Rust firmware can move faster&lt;/strong&gt; but is locked to specific SoC generations.Pluton&apos;s SoC-generation locking is the structural cost of its update-channel advantage. The Microsoft Learn Pluton page enumerates the currently supported families (AMD Ryzen 6000, 7000, 8000, 9000, and Ryzen AI Series; Intel Core Ultra 200V Series, Ultra Series 3, and (non-Ultra) Series 3 processors; Qualcomm Snapdragon 8cx Gen 3 and Snapdragon X Series) [@pluton-microsoft-learn]; OEMs without those silicon options cannot ship Pluton-backed PQC even when the firmware-update mechanism is ready. The cross-reference to the Pluton sibling article spells out the silicon-side mechanics.&lt;/p&gt;
&lt;h3&gt;9.2 Kerberos PKINIT&lt;/h3&gt;
&lt;p&gt;RFC 4556&apos;s certificate-of-the-KDC bootstrap currently uses RSA-OAEP or pre-shared-secret-via-ECDH for AS-REP key establishment. The KDC certificate could be composite-ML-DSA-signed, but the AS-REP encryption key derivation has no IETF post-quantum migration draft as of May 2026. Every Windows domain join, every smart-card logon, every Kerberos-authenticated SMB or RDP or IIS session depends on PKINIT -- and PKINIT has no PQ path. The NTLM-to-PKINIT migration (the subject of a sibling article on &lt;a href=&quot;https://paragmali.com/blog/ntlmless-the-death-of-ntlm-in-windows/&quot; rel=&quot;noopener&quot;&gt;NTLM deprecation&lt;/a&gt;) was hard enough; the PKINIT-to-PQ-PKINIT migration has not started.&lt;/p&gt;
&lt;h3&gt;9.3 Authenticode and the EFI signature database&lt;/h3&gt;
&lt;p&gt;A Windows machine that boots in 2035 must verify boot loaders signed between 2010 and 2035. The EFI signature-database revocation list (&lt;code&gt;dbx&lt;/code&gt;) is roughly 32 KB on commodity platforms [@uefi-dbx]. Replacing each entry&apos;s RSA-2048 signature with ML-DSA-65 multiplies the per-entry signature size by ~1.6×; with SLH-DSA-SHA2-128f, by ~50×. No public Microsoft &lt;a href=&quot;https://paragmali.com/blog/secure-boot-in-windows-the-chain-from-sector-zero-to-userini/&quot; rel=&quot;noopener&quot;&gt;Secure Boot&lt;/a&gt; post-quantum roadmap exists as of May 2026. LMS is the obvious candidate -- CNSA 2.0 mandates LMS or XMSS for firmware signing -- but the dbx-size question remains open. Cross-reference the Secure Boot sibling article in this series.&lt;/p&gt;
&lt;h3&gt;9.4 Algorithm agility as a separately engineered property&lt;/h3&gt;
&lt;p&gt;Section 8 limit-4 introduced algorithm agility as a structural property the IETF treats as load-bearing [@draft-pquip-engineers]. The open engineering problem is the CNG provider-interface design. Today every consumer -- Schannel, AD CS, IKEv2, SMB, RDP, Authenticode -- is wired to a specific algorithm identifier (&lt;code&gt;BCRYPT_MLKEM_ALGORITHM&lt;/code&gt;, &lt;code&gt;BCRYPT_MLDSA_ALGORITHM&lt;/code&gt;). A future migration to a NIST Round-5 KEM has to re-do every one of those wiring points, the same shape of problem CNG had with the RSA-to-ECDSA transition. Solving algorithm agility means redesigning the CNG provider interface around algorithm &lt;em&gt;families&lt;/em&gt; rather than algorithm &lt;em&gt;names&lt;/em&gt; -- a multi-year engineering programme that nobody has publicly committed to, and that the post-2030 migration window depends on.&lt;/p&gt;
&lt;h3&gt;9.5 The PKI rebuild before 2035&lt;/h3&gt;
&lt;p&gt;Every TLS server certificate, every code-signing certificate, every smart-card user certificate has to be re-issued in a post-quantum algorithm before the legacy algorithm is disallowed. The throughput of the global public-CA system is the limiting factor. Commercial CAs are pilot-issuing composite-signed roots in 2026; volume issuance lags by years. &lt;strong&gt;NIST IR 8547 (12 November 2024)&lt;/strong&gt; proposes deprecating quantum-vulnerable algorithms in NIST standards by 2035 [@nist-ir-8547].&lt;/p&gt;

NIST IR 8547 proposes the timeline; CNSA 2.0 imposes it on U.S. National Security Systems; CA/Browser Forum will eventually impose it on public web PKI. The unfunded part is the operational work. Every organisation operating an internal Windows AD CS hierarchy has to re-issue its root, its issuing CAs, and every end-entity certificate. The Microsoft tooling for this rebuild is the AD CS composite-signature support and the CertEnroll ML-DSA template path. The CA throughput question is real -- a typical commercial CA issues at peak in the low hundreds of thousands of certificates per day, and the global web PKI runs at orders of magnitude more -- which is why composite signatures are the deployment story for 2026-2030 and pure-PQ X.509 is the post-2030 story [@nist-ir-8547, @draft-lamps-composite].

flowchart TD
    OP1[&quot;TPM / Pluton blob-size limits (v1.85)&quot;]
    OP2[&quot;Kerberos PKINIT bootstrap&quot;]
    OP3[&quot;Authenticode and EFI dbx&quot;]
    OP4[&quot;CNG algorithm agility&quot;]
    OP5[&quot;Global PKI rebuild by 2035&quot;]
    OP1 --&amp;gt; S1[&quot;Hello attestation&quot;]
    OP1 --&amp;gt; S2[&quot;BitLocker network unlock&quot;]
    OP1 --&amp;gt; S3[&quot;Trustlet attestation&quot;]
    OP2 --&amp;gt; S4[&quot;Domain join&quot;]
    OP2 --&amp;gt; S5[&quot;Smart-card logon&quot;]
    OP2 --&amp;gt; S6[&quot;Kerberos-mediated SMB / RDP / IIS&quot;]
    OP3 --&amp;gt; S7[&quot;Driver loading&quot;]
    OP3 --&amp;gt; S8[&quot;Secure Boot revocation&quot;]
    OP4 --&amp;gt; S9[&quot;Schannel / AD CS / IKEv2 / SMB / RDP&quot;]
    OP5 --&amp;gt; S10[&quot;Every X.509 certificate in the estate&quot;]
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; ML-DSA-87 keys cannot live on a TPM 2.0 chip whose firmware predates Library Specification v1.85. Three Windows surfaces are stuck on RSA-2048 / ECDSA-P256 until v1.85-capable chips reach retail volume: Windows Hello attestation (TPM-bound), BitLocker network unlock (depends on TPM key sealing), and &lt;a href=&quot;https://paragmali.com/blog/vbs-trustlets-what-actually-runs-in-the-secure-kernel/&quot; rel=&quot;noopener&quot;&gt;Trustlet attestation&lt;/a&gt; (LSAISO / Credential Guard). Pluton can move faster than discrete TPMs because its firmware ships through Windows Update; the cross-reference to the Pluton sibling article explains the firmware-update agility mechanism [@wolfssl-wolftpm-v185, @ms-quantum-safe-blog].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Five open problems, five decade-scale research programmes, five places where a Windows engineer&apos;s procurement decision in 2026 will be visible in 2035. So what does that engineer do on Monday morning?&lt;/p&gt;
&lt;h2&gt;10. Practical Guide -- What an Engineer Does Monday Morning&lt;/h2&gt;
&lt;p&gt;Six actions, in priority order. Each is doable in May 2026. Each closes a real gap. None requires a procurement cycle -- those start at Action 4.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Run a CNG inventory against &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Cryptography&lt;/code&gt; and the registered providers. Catalogue certificate templates with &lt;code&gt;certutil -template&lt;/code&gt;. Enumerate Schannel cipher suites with &lt;code&gt;Get-TlsCipherSuite&lt;/code&gt; on every Schannel-using service. Identify every place RSA-2048, ECDSA-P256, ECDH/X25519, RSA-PSS, and DSA appear in your estate. Output is a CSV. The CSV is the input to every subsequent action. Without inventory, the migration is a guessing game [@cng-algorithm-ids].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{`
// Pseudo-code for a Schannel cipher-suite inventory.
// In real PowerShell: Get-TlsCipherSuite | Select-Object Name, Hash, Cipher, Exchange
// This logic flags quantum-vulnerable Exchange groups (RSA, ECDH-*-without-PQ-companion).
type CipherSuite = {
  name: string;
  exchange: string;   // &quot;ECDHE&quot;, &quot;DHE&quot;, &quot;RSA&quot;, &quot;X25519MLKEM768&quot;, ...
  cipher: string;     // &quot;AES-256-GCM&quot;, &quot;ChaCha20-Poly1305&quot;, ...
  hash: string;       // &quot;SHA-384&quot;, &quot;SHA-256&quot;, ...
};&lt;/p&gt;
&lt;p&gt;const QUANTUM_VULNERABLE_EXCHANGE = new Set([
  &quot;RSA&quot;, &quot;DHE&quot;, &quot;ECDHE&quot;, &quot;ECDH&quot;,                // classical key agreement
  &quot;X25519&quot;, &quot;SecP256r1&quot;, &quot;SecP384r1&quot;,            // unwrapped classical EC groups
]);&lt;/p&gt;
&lt;p&gt;const QUANTUM_SAFE_EXCHANGE = new Set([
  &quot;X25519MLKEM768&quot;, &quot;SecP256r1MLKEM768&quot;, &quot;SecP384r1MLKEM1024&quot;,
  &quot;MLKEM768&quot;, &quot;MLKEM1024&quot;,
]);&lt;/p&gt;
&lt;p&gt;function audit(suites: CipherSuite[]) {
  for (const s of suites) {
    if (QUANTUM_SAFE_EXCHANGE.has(s.exchange)) {
      console.log(&quot;OK    &quot; + s.name + &quot;  (&quot; + s.exchange + &quot;)&quot;);
    } else if (QUANTUM_VULNERABLE_EXCHANGE.has(s.exchange)) {
      console.log(&quot;FLAG  &quot; + s.name + &quot;  (&quot; + s.exchange + &quot; is harvest-now-decrypt-later exposed)&quot;);
    } else {
      console.log(&quot;?     &quot; + s.name + &quot;  (unknown exchange &quot; + s.exchange + &quot;)&quot;);
    }
  }
}
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Cloudflare-fronted endpoints already negotiate &lt;code&gt;X25519MLKEM768&lt;/code&gt; by default [@cloudflare-pq-for-all, @cloudflare-pq-2024]. For Windows servers using OpenSSL 3.5+, enable hybrid. For Schannel-only servers, monitor the Group Policy toggle on 24H2 and the documented Schannel curve preference order. Hybrid is the immediate harvest-now-decrypt-later defence -- the one place where a single configuration change measurably reduces today&apos;s exposure to a future quantum break.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Issue one composite root, one composite issuing CA, one composite end-entity certificate in a non-production lab. Validate consumption by an updated Schannel client and Microsoft Edge. The point is to surface the operational rough edges -- template definition, key-archival behaviour with PQ keys, certificate-validation timing on uplevel and downlevel clients -- before they hit production. This is the load-bearing 2026-2030 PKI migration on-ramp; the composite OID is downlevel-compatible, so the failure mode of an uplevel client validating an unaltered classical chain is the baseline, not a regression [@draft-lamps-composite].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Current TPM 2.0 v1.84 chips on most Windows 11 endpoints will not accept ML-DSA-87 keys without a firmware update. Use &lt;code&gt;Get-Tpm&lt;/code&gt; and the TBS API to enumerate supported algorithms; if &lt;code&gt;BCryptOpenAlgorithmProvider&lt;/code&gt; for ML-DSA returns &lt;code&gt;NTE_NOT_SUPPORTED&lt;/code&gt; against the platform crypto provider, the underlying TPM does not yet expose the PQ surface. If your hardware lifetime extends past 2030, wait for v1.85-capable chips (early sampling 2026) or Pluton (already shipping with on-die firmware updates). Inventory your fleet&apos;s TPM firmware versions today; the migration plan needs to know the floor [@wolfssl-wolftpm-v185].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; UEFI capsule signing, OEM driver signing, and secure-boot dbx revocation entries are all candidates for LMS or XMSS [@cnsa20-csa]. The stateful-counter requirement is acceptable because build pipelines already track build numbers monotonically. The CNG identifier &lt;code&gt;BCRYPT_LMS_ALGORITHM&lt;/code&gt; is prerelease; SymCrypt v103.6.0 ships LMS [@symcrypt-changelog]. Start the pilot in a non-production signing service that has secure HSM custody of the counter state. The firmware-signing migration is the only place where CNSA 2.0 explicitly &lt;em&gt;prefers&lt;/em&gt; stateful hash-based signatures over ML-DSA, because firmware signing is the use case where state discipline is realistic.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Post-quantum Authenticode is not specified by Microsoft as of May 2026. Premature migration breaks downlevel verification. The discipline is: hybrid TLS first, composite X.509 chain second, AD CS pilot third, firmware-signing pilot fourth, and &lt;strong&gt;leave Authenticode alone&lt;/strong&gt; until Microsoft publishes the post-quantum Authenticode specification. Authenticode signatures are validated at binary load time; harvest-now-decrypt-later does not apply, and there is no urgency that justifies risking downlevel-verifier breakage. This is the action that takes restraint rather than effort.&lt;/p&gt;
&lt;/blockquote&gt;

The priority ordering follows the threat model. Hybrid TLS is first because it closes harvest-now-decrypt-later on transport traffic with no compatibility cost (the classical share remains in the handshake; an uplevel server downgrades to classical-only cleanly). Composite X.509 is second because it lets you build a post-quantum-ready PKI hierarchy now and surfaces operational rough edges before pure-PQ deployments. Firmware signing is third because the stateful-counter discipline requires HSM-mediated key custody and a long lead time for the signing pipeline. Authenticode is last because there is no specification and no urgency.
&lt;p&gt;One quarter to inventory, two quarters to pilot, two years to volume. Now for the questions every engineer asks after reading.&lt;/p&gt;
&lt;h2&gt;11. Frequently Asked Questions and Closing&lt;/h2&gt;


Yes, when the server supports `X25519MLKEM768` and your Edge build negotiates it. Check via the Edge devtools&apos; Security panel, or test against a Cloudflare-fronted endpoint with the Edge URL bar&apos;s connection information popup. Cloudflare reported nearly two percent of all TLS 1.3 connections to its edge were post-quantum-protected in March 2024, with a forecast of double-digit adoption by year-end [@cloudflare-pq-2024]. Endpoints that have not enabled hybrid TLS still negotiate X25519 alone, which leaves you exposed to harvest-now-decrypt-later.

Not as of May 2026. Schannel&apos;s hybrid TLS 1.3 preview is gated behind Group Policy on 24H2 and Server 2025. Microsoft&apos;s Quantum-Safe Security blog frames May 2026 to November 2026 as the milestone window for default-on negotiation [@ms-quantum-safe-blog]. Until then, the Schannel side has to be explicitly opted in via the cipher-preference order and the Group Policy toggle.

No, not on the public record. Post-quantum Authenticode is not yet specified. The CNG `BCRYPT_MLDSA_ALGORITHM` exists, and SymCrypt 103.7.0 implements ML-DSA, but the Authenticode signature format and the verifier policy have not been updated to accept post-quantum algorithms. Premature migration breaks downlevel verification on every Windows machine that has not received the PQ Authenticode update -- which today is *every* Windows machine. Do not migrate Authenticode prematurely.

Only against harvest-now-decrypt-later. Today&apos;s TLS is not under quantum attack -- quantum computers capable of breaking RSA-2048 or ECDH-X25519 do not exist in 2026. But today&apos;s TLS *traffic can be recorded* today, and if a sufficient quantum computer exists in 2040 the recorded traffic can be decrypted then. Enabling hybrid TLS now closes that window; enabling it in 2035 does not retroactively protect the traffic recorded in 2026 [@mosca-2015].

Nobody knows. CNSA 2.0 picks 2035 as the policy deadline, not a technical forecast [@cnsa20-csa]. Mosca&apos;s 2015 estimate, widely reproduced in PQC literature, was a 1/2 chance of breaking RSA-2048 by 2031. Quantum-engineering progress between 2015 and 2026 has been substantial on the qubit-count axis and modest on the error-correction axis; the underlying question -- when a thousand-logical-qubit fault-tolerant device becomes available -- has no consensus answer in 2026. CNSA 2.0&apos;s job is to make the answer not matter.

Hybrid `X25519MLKEM768` protects you against a break of either component. The HKDF combiner in `draft-ietf-tls-hybrid-design-16` requires both component shared secrets to be uniform-looking from the adversary&apos;s perspective; an attacker who breaks only ML-KEM still cannot recover the session key without also breaking X25519 [@draft-tls-hybrid]. Pure ML-KEM (no hybrid) does not have this property. The central design choice of the IETF hybrid construction is that it pays the byte cost (~1.2 KB extra per handshake) to buy the safety margin.

The kilobyte scale is structural to lattice mathematics; it is not a parameter-tuning issue. The minimum key size for a Module-LWE-based KEM at NIST category 3 is set by the dimension required for security under the best known lattice-sieving attacks. ML-KEM-768 at 1184 bytes is already aggressively tuned [@wp-kyber]. The alternatives that offer smaller keys (Falcon at 897 bytes, SIKE-when-it-was-alive at ~330 bytes) buy that size with either constant-time difficulty (Falcon&apos;s Gaussian sampler) or fragility (SIKE collapsed in July 2022). Classical comparisons: X25519&apos;s 32-byte public value is the floor; Classic McEliece&apos;s ~1 MB is the ceiling.

Only if your procurement timeline extends past 2030. As of May 2026, TPM 2.0 v1.85-PQ-spec-compliant chips are in announcement and early-sampling stages, not in retail volume [@wolfssl-wolftpm-v185]. Pluton is shipping, but is locked to specific SoC generations (current Intel Core Ultra series, AMD Ryzen 8000-series, Qualcomm Snapdragon X) [@pluton-microsoft-learn]. If you replace endpoints every three years, your 2026 procurement decision will be visible in 2029, before any post-quantum TPM mandate bites. If your refresh cycle is five-to-seven years, the calculus changes -- but the answer is still &quot;wait for v1.85 silicon to ship at volume&quot; unless you can write a specific business case for the early-adopter risk.

&lt;h3&gt;Closing&lt;/h3&gt;
&lt;p&gt;A Windows endpoint opens a connection to &lt;code&gt;cloudflare.com&lt;/code&gt;. The 1184-byte field on the wire is no longer a curiosity. It is a thirty-year migration in a single TLS extension. The bytes have a history: Diffie and Hellman in 1976; Shor in 1994; McEliece&apos;s megabyte keys; HFE and its descendants broken by Beullens; NTRU patented for two decades; Regev&apos;s quantum-reduction LWE in 2005; the Ring-LWE compression of 2010; the Module-LWE knob of 2012; BCNS 2014 from Microsoft Research Redmond; Cloudflare-by-default on October 3, 2022; the Castryck-Decru break twenty-five days after NIST&apos;s July 2022 selection; SymCrypt 103.5.0; the FIPS publications of August 13, 2024; the CNG &lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt; exposed in Insider Canary in May 2025; Schannel preview behind Group Policy in early 2026.&lt;/p&gt;
&lt;p&gt;The work is not done. Kerberos PKINIT has no PQ path. Authenticode has no PQ specification. BitLocker network unlock is still RSA-2048. The EFI signature database is still RSA-2048. Every signed binary already on every Windows disk in the world is signed with an algorithm whose 2035 status is uncertain. The TPM 4096-byte buffer cannot fit an ML-DSA-87 signature. CNG ships per-algorithm identifiers, not per-algorithm-class, which guarantees that the &lt;em&gt;next&lt;/em&gt; migration will hit the same surfaces from the same angle. CNSA 2.0 picks 2035; NIST IR 8547 picks 2035 [@cnsa20-csa, @nist-ir-8547]; the global public-CA infrastructure has nine years to rebuild every certificate it has ever issued.&lt;/p&gt;

Migration to post quantum cryptography (PQC) is not a flip-the-switch moment, it&apos;s a multiyear transformation that requires immediate planning and coordinated execution to avoid a last-minute scramble. -- Microsoft Quantum-Safe Security blog, 20 August 2025 [@ms-quantum-safe-blog].

The cryptographic transition described here runs in parallel to the architectural transition documented across this blog&apos;s sibling articles. The hypervisor article explains the substrate on which the Secure Kernel and trustlets sit. The VBS trustlets article explains where Credential Guard lives. The NTLM-to-Kerberos article documents the protocol migration that PQ Kerberos PKINIT will eventually re-do. The Adminless article addresses the local-administrator surface; the Pluton and TPM articles cover the silicon-side roots of trust; the Secure Boot article covers the static measured boot chain that meets the dynamic measured boot chain at hypervisor load. Read them in any order. They share the same migration calendar, the same engineering discipline, and the same honesty about the gaps.
&lt;p&gt;Above all, the bytes are real. The CNG handle exists. The SymCrypt release is shipping. The migration has started. The next decade is the engineering. Every line of code, every parameter set, every byte of that 1184-byte field has thirty years of work behind it, and the Windows engineer of 2026 is the one who carries it the next mile.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;post-quantum-cryptography-on-windows&quot; keyTerms={[
  { term: &quot;ML-KEM (FIPS 203)&quot;, definition: &quot;Module-Lattice-Based Key-Encapsulation Mechanism. The only NIST-finalised post-quantum KEM. Parameter sets ML-KEM-512/768/1024 over R_q = Z_q[X]/(X^256 + 1) with q = 3329. ML-KEM-768 public key is 1184 bytes, ciphertext 1088 bytes, shared secret 32 bytes.&quot; },
  { term: &quot;ML-DSA (FIPS 204)&quot;, definition: &quot;Module-Lattice-Based Digital Signature Algorithm. Fiat-Shamir-with-aborts over Module-LWE / Module-SIS, q = 8380417. Parameter sets ML-DSA-44/65/87; signatures 2420 / 3293 / 4595 bytes.&quot; },
  { term: &quot;SLH-DSA (FIPS 205)&quot;, definition: &quot;Stateless Hash-Based Digital Signature Algorithm. SPHINCS+ lineage; security rests on hash-function security alone. Twelve parameter sets across SHA-2 and SHAKE; signatures 7,856 to 49,856 bytes.&quot; },
  { term: &quot;LWE / Ring-LWE / Module-LWE&quot;, definition: &quot;Learning With Errors: distinguish (A, As + e) from uniform when e is small. Ring-LWE lifts to a polynomial ring; Module-LWE generalises to module-rank-k. The 2010-2012 algebraic lift compressed lattice key sizes from megabytes to kilobytes.&quot; },
  { term: &quot;NTT&quot;, definition: &quot;Number Theoretic Transform. The finite-field analogue of the FFT; reduces polynomial multiplication in R_q from O(n^2) to O(n log n). The reason ML-KEM is fast enough for TLS.&quot; },
  { term: &quot;Fujisaki-Okamoto-Hofheinz transform&quot;, definition: &quot;Generic IND-CPA-to-IND-CCA2 transform for KEMs. Re-encrypts the plaintext during decapsulation and returns implicit-rejection pseudorandom output on mismatch. ML-KEM wraps K-PKE with FO to become IND-CCA2.&quot; },
  { term: &quot;Mosca&apos;s inequality&quot;, definition: &quot;X + Y &amp;gt; Z. If data-secrecy lifetime plus migration time exceeds time-to-quantum-computer, harvest-now-decrypt-later succeeds. The framing that made post-quantum migration an actionable IT-policy lever.&quot; },
  { term: &quot;CNSA 2.0&quot;, definition: &quot;U.S. NSA Commercial National Security Algorithm Suite 2.0. Mandates ML-KEM-1024, ML-DSA-87, LMS/XMSS for firmware, AES-256, and SHA-384 in National Security Systems. Acquisition preference 2027; mandatory adoption 2031; RSA / ECDSA disallowed after 2035.&quot; },
  { term: &quot;Hybrid key agreement&quot;, definition: &quot;Combines a classical key-agreement primitive (X25519) with a post-quantum KEM (ML-KEM-768) so the session key depends on both. An adversary must break both components to forge or recover. Used by Cloudflare since October 2022, Apple iMessage PQ3 since February 2024, Schannel preview in 2026.&quot; },
  { term: &quot;Composite signature&quot;, definition: &quot;X.509 signature that combines a classical and a post-quantum component such that both must verify. The deployment story for 2026-2030 X.509 PKI migration, per draft-ietf-lamps-pq-composite-sigs-19. Downlevel verifiers ignore the composite OID; uplevel verifiers validate both.&quot; },
  { term: &quot;Algorithm agility&quot;, definition: &quot;The property that protocols and APIs can change the underlying algorithm without re-engineering the consumer. CNG ships per-algorithm identifiers (BCRYPT_MLKEM_ALGORITHM) rather than per-algorithm-class identifiers; a future Round-5 KEM will require new CNG plumbing.&quot; },
  { term: &quot;X25519MLKEM768&quot;, definition: &quot;Hybrid TLS 1.3 Supported Group, codepoint 0x11EC, defined in draft-ietf-tls-ecdhe-mlkem-04. Concatenates X25519 (32-byte) and ML-KEM-768 (1184-byte ek / 1088-byte ct) shares. ClientHello key_share is 1216 bytes; ServerHello key_share is 1120 bytes.&quot; }
]} questions={[
  { q: &quot;Why is the post-quantum programme a public-key replacement programme and not a symmetric one?&quot;, a: &quot;Shor&apos;s algorithm breaks RSA / DH / ECDSA / ECDH in polynomial time on a fault-tolerant quantum computer, with no parameter increase rescuing them. Grover&apos;s algorithm gives only a quadratic speedup on symmetric primitives, which is absorbed by doubling key sizes (AES-256, SHA-384). The asymmetric lane is fatal; the symmetric lane is a parameter bump.&quot; },
  { q: &quot;What single algebraic move compressed lattice public keys from megabytes to kilobytes?&quot;, a: &quot;Lifting Learning With Errors from Z_q to a polynomial ring R_q = Z_q[X]/(X^n + 1), per Lyubashevsky-Peikert-Regev 2010. Polynomial multiplication via NTT becomes O(n log n) instead of O(n^2). Module-LWE (Langlois-Stehle 2012/2015) added a module-rank parameter knob that lets one base ring serve every NIST security category.&quot; },
  { q: &quot;Why does the NIST FIPS slate combine a lattice scheme and a hash scheme rather than two lattice schemes?&quot;, a: &quot;Diversification. Both the Rainbow break (Beullens, February 2022) and the SIKE break (Castryck-Decru, July 2022) happened during the NIST competition. The portfolio rests on two structurally unrelated foundations (lattice + hash) so that a single mathematical break cannot retire the whole programme. SLH-DSA&apos;s security rests on hash-function security alone.&quot; },
  { q: &quot;Which SymCrypt version first added ML-KEM?&quot;, a: &quot;Version 103.5.0, which &apos;Add ML-KEM per final FIPS 203&apos; along with XMSS and XMSS^MT. Subsequent versions added LMS (103.6.0), ML-DSA (103.7.0), the FIPS approved-services indicator (103.8.0), ML-DSA External-Mu (103.9.0), FIPS CAST for ML-DSA (103.9.1), and Composite ML-KEM (103.11.0).&quot; },
  { q: &quot;What TLS Supported Group does Schannel preview-negotiate on 24H2, and what is its codepoint?&quot;, a: &quot;X25519MLKEM768, codepoint 0x11EC, per draft-ietf-tls-ecdhe-mlkem-04 (8 February 2026). The ClientHello carries 32 bytes X25519 + 1184 bytes ML-KEM-768 encapsulation key (1216 bytes total); the ServerHello carries 32 bytes X25519 + 1088 bytes ML-KEM-768 ciphertext (1120 bytes total).&quot; },
  { q: &quot;Why does ML-DSA-87 not fit on a commodity TPM 2.0 chip?&quot;, a: &quot;ML-DSA-87 signatures are 4595 bytes. Default TPM 2.0 MAX_COMMAND_SIZE and MAX_RESPONSE_SIZE are 4096 bytes. TCG TPM 2.0 Library Specification v1.85 (March 2026) introduces a streaming TPM2_SignSequence Start / Complete family (with TPM2_SignDigest / TPM2_VerifyDigestSignature for digest-mode operations) and ML-KEM TPM2_Encapsulate / Decapsulate, but v1.85-capable chips are in early sampling in 2026, not retail volume.&quot; },
  { q: &quot;What is the difference between LMS / XMSS and SLH-DSA, and when does CNSA 2.0 prefer each?&quot;, a: &quot;LMS and XMSS are stateful: the signer must track a counter and never reuse a leaf index. SLH-DSA derives the leaf address from the message hash and per-signature randomness, making it stateless. CNSA 2.0 specifies LMS or XMSS for firmware signing (where build pipelines already track counters under HSM custody) and ML-DSA-87 for general signing.&quot; },
  { q: &quot;Why does PQC not close the signed-binary persistence channel?&quot;, a: &quot;Authenticode signatures are validated at binary load time. A 2026 RSA-2048 signature has already been verified by every machine that downloaded the binary; a 2035 quantum break does not retroactively decrypt anything because Authenticode is not an encryption channel. The threat model is forgery of new signatures on new binaries, not retroactive decryption. Harvest-now-decrypt-later does not apply.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows</category><category>cryptography</category><category>post-quantum</category><category>pqc</category><category>tls</category><category>symcrypt</category><category>cng</category><category>fips</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>