<?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: fips</title><description>Posts tagged fips.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 07 Jun 2026 04:13:12 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/fips/rss.xml" rel="self" type="application/rss+xml"/><item><title>CNG Architecture: BCrypt, NCrypt, KSPs, and How Windows Picks Its Algorithms</title><link>https://paragmali.com/blog/cng-architecture-bcrypt-ncrypt-ksps/</link><guid isPermaLink="true">https://paragmali.com/blog/cng-architecture-bcrypt-ncrypt-ksps/</guid><description>A guided tour of the Cryptography API: Next Generation -- the two-tier API, the Key Storage Provider model, the FIPS toggle, and how PQC slots in.</description><pubDate>Sat, 16 May 2026 00:00:00 GMT</pubDate><content:encoded>
Since Windows Vista, every piece of cryptography in Windows -- TLS, BitLocker, Authenticode, Windows Hello, DPAPI -- flows through the **Cryptography API: Next Generation (CNG)**. CNG splits the world into two layers. **BCrypt** does primitives: AES, SHA, HMAC, RNG, key derivation. **NCrypt** routes calls to a **Key Storage Provider (KSP)** that owns the long-lived private keys: software, TPM, smart card, or a third-party HSM. Algorithm selection is governed by a registered provider-priority list, the Schannel cipher-suite order, and a single FIPS-mode toggle that flips Windows into its validated subset. Windows 11 24H2 added the first post-quantum primitives (ML-KEM, ML-DSA) to the same surface, with no API break. This article walks through how that machine works, why Microsoft designed it that way, and where it leaks.
&lt;h2&gt;1. From CAPI to CNG: why Microsoft started over&lt;/h2&gt;
&lt;p&gt;In the late 1990s, Microsoft shipped its first general cryptographic API. The original Cryptographic Service Providers (CAPI) model [@learn-microsoft-com-service-providers] arrived in Windows NT 4.0 Service Pack 4 in 1998 and defined a plug-in unit called a Cryptographic Service Provider, or CSP. A CSP was a monolithic DLL: it owned the algorithm implementations, the key storage, and the export-control posture all at once. If you wanted to add hardware-backed RSA on Windows NT, you wrote a CSP. If you wanted to add a new hash function, you also wrote a CSP. The model worked for the algorithms Microsoft had in mind when it designed it.&lt;/p&gt;
&lt;p&gt;Then the algorithms changed.&lt;/p&gt;
&lt;p&gt;AES was standardized in 2001, after CAPI&apos;s design was already frozen. Microsoft retrofitted AES into the original architecture by shipping the Microsoft Enhanced RSA and AES Cryptographic Provider [@learn-microsoft-com-cryptographic-provider] as a separate CSP, sitting alongside the original Microsoft Base Cryptographic Provider. Elliptic-curve cryptography was even more awkward: CAPI&apos;s algorithm identifiers and key-blob formats had no place for ECC curves. Every new algorithm required a new CSP or a new release of an existing one. The plug-in surface was rigid, the FIPS validation story was painful, and the API was relentlessly C-shaped in ways that made auditing hard.Microsoft was not alone. The same era produced Intel&apos;s Common Data Security Architecture (CDSA) [@en-wikipedia-org-os-2] and several short-lived crypto frameworks for OS/2 and other platforms. Most of them disappeared. CAPI&apos;s longevity owed more to Windows market share than to its design.&lt;/p&gt;
&lt;p&gt;By 2005, Microsoft started over. The result was the Cryptography API: Next Generation, or CNG, which shipped with Windows Vista and Windows Server 2008 in January 2007 [@learn-microsoft-com-cng-portal]. CNG was not a refactor. It was a clean second system, designed from a different set of assumptions: algorithms would keep arriving, key storage needed to be a separate concern, FIPS validation had to be a first-class output, and the same API had to work in user mode and kernel mode.&lt;/p&gt;

The Windows cryptographic API introduced in Vista (2007) as the long-term replacement for CAPI. CNG splits cryptography into a primitives layer (`bcrypt.h`, `bcryptprimitives.dll`) and a key-storage layer (`ncrypt.h`, `ncrypt.dll`), each pluggable through registered providers. Used by every modern Windows component that touches cryptography.

The plug-in unit of the legacy CAPI architecture (1998-onward). A CSP bundled algorithms, key storage, and FIPS posture into a single DLL. Largely superseded by CNG providers, but still present on the system for backwards compatibility.
&lt;p&gt;The three design pillars Microsoft committed to in the CNG portal documentation were modularity, cryptographic agility, and FIPS-compliance readiness [@learn-microsoft-com-cng-features]. All three would matter twenty years later when post-quantum cryptography arrived without warning the protocol authors. We will get to that.&lt;/p&gt;

Throughout this article, &quot;BCrypt&quot; refers to Microsoft&apos;s CNG primitives header `bcrypt.h` and its companion DLL `bcryptprimitives.dll`. It is not the Provos-Mazieres password-hashing function of the same name, which is unrelated and uses a different spelling in most academic literature (&quot;bcrypt&quot;). The naming collision is unfortunate but firmly entrenched in Windows.
&lt;h2&gt;2. BCrypt: the symmetric stack and the ephemeral key&lt;/h2&gt;
&lt;p&gt;Open a Visual Studio project, include &lt;code&gt;&amp;lt;bcrypt.h&amp;gt;&lt;/code&gt;, link &lt;code&gt;bcrypt.lib&lt;/code&gt;, and you have access to almost every cryptographic primitive Windows ships. AES in CBC, CFB, ECB, GCM, and CCM modes. SHA-1, SHA-256, SHA-384, SHA-512, the SHA-3 family, and the cSHAKE128 and cSHAKE256 extendable-output functions added in Windows 11 24H2 [@learn-microsoft-com-algorithm-identifiers]. HMAC over any of those hashes. PBKDF2. The NIST SP 800-108 key-derivation construction. The DRBG-based random number generator drawn from NIST SP 800-90 [@csrc-nist-gov-1-final]. Ephemeral asymmetric operations -- RSA encrypt, ECDSA sign, ECDH key agreement -- on key handles that vanish when the process exits.&lt;/p&gt;
&lt;p&gt;The canonical BCrypt opening dance is four calls.&lt;/p&gt;
&lt;p&gt;{`
// Pseudocode mirroring the BCryptOpenAlgorithmProvider flow.
// In real C: NTSTATUS values, BCRYPT_ALG_HANDLE, etc.&lt;/p&gt;
&lt;p&gt;const algId       = &quot;AES&quot;;           // wide string
const impl        = null;            // null -&amp;gt; walk the priority list
const flags       = 0;&lt;/p&gt;
&lt;p&gt;const hAlg        = BCryptOpenAlgorithmProvider(algId, impl, flags);
BCryptSetProperty(hAlg, &quot;ChainingMode&quot;, &quot;ChainingModeGCM&quot;);&lt;/p&gt;
&lt;p&gt;const hKey        = BCryptGenerateSymmetricKey(hAlg, keyBytes);
const ciphertext  = BCryptEncrypt(hKey, plaintext, authInfo);&lt;/p&gt;
&lt;p&gt;BCryptDestroyKey(hKey);
BCryptCloseAlgorithmProvider(hAlg, 0);
`}&lt;/p&gt;
&lt;p&gt;The interesting parameter is &lt;code&gt;impl&lt;/code&gt;. When it is &lt;code&gt;NULL&lt;/code&gt;, &lt;code&gt;BCryptOpenAlgorithmProvider&lt;/code&gt; &quot;attempts to open each registered provider, in order of priority, for the algorithm specified by the pszAlgId parameter and returns the handle of the first provider that is successfully opened&quot; [@learn-microsoft-com-bcrypt-bcryptopenalgorithmprovider]. That sentence is the whole story of CNG provider priority in nineteen words.&lt;/p&gt;
&lt;p&gt;Algorithm identifiers are wide strings. &lt;code&gt;L&quot;AES&quot;&lt;/code&gt;, &lt;code&gt;L&quot;SHA256&quot;&lt;/code&gt;, &lt;code&gt;L&quot;RSA&quot;&lt;/code&gt;, &lt;code&gt;L&quot;ML-KEM&quot;&lt;/code&gt;, &lt;code&gt;L&quot;ML-DSA&quot;&lt;/code&gt;, &lt;code&gt;L&quot;CHACHA20_POLY1305&quot;&lt;/code&gt;, &lt;code&gt;L&quot;CSHAKE128&quot;&lt;/code&gt;. Each string is registered in CNG&apos;s configuration store under &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\&lt;/code&gt;, with a per-algorithm ordered list of providers that claim to implement it. Add a new algorithm and you add a new string. Add a new provider and you append to its priority list. The API surface does not change.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The algorithm-identifier string is the seam where cryptographic agility lives. As long as your protocol can encode &quot;use whatever the spec calls AES-256-GCM,&quot; and as long as a CNG provider answers to that name, you can swap implementations without touching the calling code. Protocols whose wire format hard-codes the algorithm (the old SSL 3.0 cipher list, for example) do not get this benefit no matter what crypto API they call.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Underneath the API is a single implementation library. Microsoft&apos;s SymCrypt [@github-com-microsoft-symcrypt] has been the actual workhorse since Windows 10 version 1703: &quot;SymCrypt is the core cryptographic function library currently used by Windows... Since the 1703 release of Windows 10, SymCrypt has been the primary crypto library for all algorithms in Windows.&quot; SymCrypt is open source. It carries hand-tuned assembly for AES-NI, VAES, SHA-NI, and PCLMULQDQ on x64, plus ARM64 SHA and AES intrinsics. On a modern Xeon, AES-GCM throughput from BCrypt routinely sits in the 4 to 8 GB/s range per core.&lt;/p&gt;
&lt;p&gt;SymCrypt&apos;s open-source release in 2019 was a quiet event for a Microsoft library: the algorithms that protect Windows are reviewable by anyone willing to read C and ARM/x64 assembly.&lt;/p&gt;
&lt;p&gt;BCrypt keys are ephemeral by construction. A &lt;code&gt;BCRYPT_KEY_HANDLE&lt;/code&gt; lives in your process and dies with it. If you want to keep a private key around between processes, between reboots, or between machines, you do not use BCrypt. You use NCrypt.&lt;/p&gt;
&lt;p&gt;That distinction is the first thing developers get wrong when they meet CNG. The second thing they get wrong is forgetting that BCrypt&apos;s GCM API does not allocate nonces for you. The NIST SP 800-38D specification of Galois/Counter Mode [@nvlpubs-nist-gov-nistspecialpublication800-38dpdf] is famously brittle under nonce reuse: a single repeated nonce under the same key destroys both confidentiality (XOR of plaintexts leaks) and authenticity (the GHASH authentication key becomes recoverable). With 96-bit random nonces the birthday bound limits safe usage to roughly $2^{32}$ invocations per key before collision probability becomes meaningful. Counter-based nonces sidestep the birthday bound entirely but require persistent state. CNG does neither for you. That part is your problem.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; First, &lt;strong&gt;GCM nonce reuse&lt;/strong&gt;: &lt;code&gt;BCryptEncrypt&lt;/code&gt; with &lt;code&gt;BCRYPT_CHAIN_MODE_GCM&lt;/code&gt; accepts whatever 12 bytes you hand it. Counter or random, but never twice. Second, &lt;strong&gt;algorithm string drift&lt;/strong&gt;: &lt;code&gt;BCRYPT_SHA256_ALGORITHM&lt;/code&gt; is the macro for &lt;code&gt;L&quot;SHA256&quot;&lt;/code&gt;. &lt;code&gt;L&quot;SHA-256&quot;&lt;/code&gt; returns &lt;code&gt;STATUS_NOT_FOUND&lt;/code&gt;. Third, &lt;strong&gt;kernel-mode pseudo-handles&lt;/strong&gt;: the convenient &lt;code&gt;BCRYPT_AES_ALG_HANDLE&lt;/code&gt; shortcut is user-mode only per the BCryptOpenAlgorithmProvider remarks [@learn-microsoft-com-bcrypt-bcryptopenalgorithmprovider]; kernel drivers must use real handles.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Windows 10 added pseudo-handles -- pre-baked handle constants like &lt;code&gt;BCRYPT_AES_ALG_HANDLE&lt;/code&gt; and &lt;code&gt;BCRYPT_SHA256_ALG_HANDLE&lt;/code&gt; -- that skip the provider lookup for the built-in algorithms. The 24H2 release extended that list to include &lt;code&gt;BCRYPT_MLKEM_ALG_HANDLE&lt;/code&gt; and the cSHAKE handles. Microsoft now recommends pseudo-handles over &lt;code&gt;BCryptOpenAlgorithmProvider&lt;/code&gt; for new code [@learn-microsoft-com-bcrypt-bcryptopenalgorithmprovider] when the algorithm is built in. The motivation is performance: pseudo-handles bypass the per-call provider walk and the configuration-store lookup.&lt;/p&gt;
&lt;p&gt;That covers the primitives. Now we need a place to keep the keys.&lt;/p&gt;
&lt;h2&gt;3. NCrypt: where the long-lived secrets live&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;ncrypt.h&lt;/code&gt; header opens a different door. Every function in the NCrypt API surface [@learn-microsoft-com-api-ncrypt] -- &lt;code&gt;NCryptOpenStorageProvider&lt;/code&gt;, &lt;code&gt;NCryptCreatePersistedKey&lt;/code&gt;, &lt;code&gt;NCryptOpenKey&lt;/code&gt;, &lt;code&gt;NCryptSignHash&lt;/code&gt;, &lt;code&gt;NCryptDecrypt&lt;/code&gt;, &lt;code&gt;NCryptKeyDerivation&lt;/code&gt;, &lt;code&gt;NCryptExportKey&lt;/code&gt;, &lt;code&gt;NCryptProtectSecret&lt;/code&gt; -- begins by routing the call through &lt;code&gt;ncrypt.dll&lt;/code&gt;, which acts as a router rather than an implementation. The router decides which Key Storage Provider handles the operation and forwards the call.&lt;/p&gt;
&lt;p&gt;That routing layer is the architectural distinction Microsoft has insisted on for two decades. Microsoft&apos;s Key Storage and Retrieval documentation [@learn-microsoft-com-and-retrieval] describes it like this: the NCrypt router &quot;conceals details, such as key isolation, from both the application and the storage provider itself.&quot; Translation: the application calls &lt;code&gt;NCryptSignHash&lt;/code&gt; and gets back a signature. It does not know -- and should not need to know -- whether the key lives in &lt;code&gt;%APPDATA%&lt;/code&gt;, inside a TPM chip on the motherboard, on a smart card halfway across the room, or in a network-attached hardware security module in a data center on a different continent.&lt;/p&gt;

A registered plug-in DLL that owns persistent private-key material and exposes it through the NCrypt API. Microsoft ships four built-in KSPs (Software, Platform/TPM, Smart Card, and the CNG-DPAPI provider); third parties ship KSPs for HSM appliances, USB security keys, and cloud key services. Selecting a KSP is a matter of passing the right name string to `NCryptOpenStorageProvider`.
&lt;p&gt;The mechanical flow for creating a persisted key looks like this.&lt;/p&gt;

sequenceDiagram
    participant App as Application
    participant Router as ncrypt.dll (NCrypt router)
    participant KSP as Microsoft Software KSP
    participant LSA as LSA key-isolation process
    participant Disk as %APPDATA%\Microsoft\Crypto\Keys\&lt;pre&gt;&lt;code&gt;App-&amp;gt;&amp;gt;Router: NCryptOpenStorageProvider(&quot;Microsoft Software Key Storage Provider&quot;)
Router--&amp;gt;&amp;gt;App: hProvider
App-&amp;gt;&amp;gt;Router: NCryptCreatePersistedKey(hProvider, &quot;RSA&quot;, &quot;MyKey&quot;, 2048, ...)
Router-&amp;gt;&amp;gt;KSP: dispatch via registered KSP entry points
KSP-&amp;gt;&amp;gt;LSA: LRPC: generate key, return handle
LSA-&amp;gt;&amp;gt;Disk: write DPAPI-wrapped private blob
LSA--&amp;gt;&amp;gt;KSP: ok
KSP--&amp;gt;&amp;gt;Router: hKey
Router--&amp;gt;&amp;gt;App: hKey
App-&amp;gt;&amp;gt;Router: NCryptSignHash(hKey, digest)
Router-&amp;gt;&amp;gt;KSP: forward
KSP-&amp;gt;&amp;gt;LSA: LRPC: sign with isolated key
LSA--&amp;gt;&amp;gt;KSP: signature
KSP--&amp;gt;&amp;gt;Router: signature
Router--&amp;gt;&amp;gt;App: signature
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two facts about that diagram matter. First, the private key bits never enter the calling process. They are generated inside the LSA process and the calling application only ever receives a handle and the eventual signature. Second, the LRPC hop is real: it costs roughly 30 to 100 microseconds per call on modern hardware. For bulk symmetric encryption you would not want this overhead, which is why CNG&apos;s design pushes you toward BCrypt for symmetric work and reserves NCrypt for the rarer, smaller, and more sensitive operations on long-lived asymmetric keys.The LSA key-isolation process is &lt;code&gt;lsaiso.exe&lt;/code&gt; on systems with Credential Guard enabled, hosted inside the Virtualization-Based Security (VBS) trustlet boundary. On systems without VBS, the role is played by &lt;code&gt;lsass.exe&lt;/code&gt; itself. Either way, key material does not enter the application&apos;s address space.&lt;/p&gt;
&lt;p&gt;NCrypt is also where the asymmetric algorithms live in their persistent form. The Microsoft Software Key Storage Provider claims RSA keys from 512 to 16384 bits in 64-bit increments, DSA, DH, and ECDSA/ECDH on the NIST P-256, P-384, and P-521 curves [@learn-microsoft-com-and-retrieval]. Windows 11 24H2 added ML-KEM at the 512, 768, and 1024 parameter sets and ML-DSA at the 44, 65, and 87 parameter sets to the Software KSP&apos;s repertoire.&lt;/p&gt;
&lt;p&gt;The split between BCrypt and NCrypt is sometimes confusing because there is overlap. You can sign with BCrypt&apos;s &lt;code&gt;BCryptSignHash&lt;/code&gt; if you generated an ephemeral key pair. You can also sign with NCrypt&apos;s &lt;code&gt;NCryptSignHash&lt;/code&gt; if the key is persisted in a KSP. The rule of thumb is: if the key needs to survive the process, use NCrypt; if it does not, use BCrypt. Real-world Windows code skews heavily toward NCrypt for asymmetric operations because almost every interesting asymmetric key has an associated certificate, and certificates outlive processes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The four Microsoft KSP name strings are &lt;code&gt;MS_KEY_STORAGE_PROVIDER&lt;/code&gt; (Software), &lt;code&gt;MS_PLATFORM_KEY_STORAGE_PROVIDER&lt;/code&gt; (TPM/Pluton), &lt;code&gt;MS_SMART_CARD_KEY_STORAGE_PROVIDER&lt;/code&gt;, and &lt;code&gt;MS_NGC_KEY_STORAGE_PROVIDER&lt;/code&gt; (Next Generation Credentials, used by Windows Hello). Typo any of these and you silently fall through to the Software KSP, which is a recurring source of &quot;why is my key on disk instead of in the TPM&quot; incident reports.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The router lets the application speak one language and have the storage backend vary. That makes the KSP plug-in model the most interesting piece of the architecture, and it deserves its own section.&lt;/p&gt;
&lt;h2&gt;4. The KSP model: one API, many places to keep keys&lt;/h2&gt;
&lt;p&gt;A KSP is a DLL on disk and an entry in the registry. The DLL exports a fixed set of function pointers that mirror NCrypt&apos;s API. The registry entry under &lt;code&gt;HKLM\SOFTWARE\Microsoft\Cryptography\Providers\Microsoft Software Key Storage Provider&lt;/code&gt; (and its siblings) tells &lt;code&gt;ncrypt.dll&lt;/code&gt; which DLL to load when an application asks for a provider by name. That is the whole interface contract. If you can produce a DLL that implements the entry points and you can install a registry entry, you have a CNG KSP.&lt;/p&gt;
&lt;p&gt;The platform comes with four. They sit on a spectrum from &quot;your operating system is the entire trust boundary&quot; to &quot;the keys live on a separate piece of silicon and only signatures come back.&quot;&lt;/p&gt;

flowchart LR
    A[&quot;Microsoft Software KSP -- private keys on disk -- (DPAPI-wrapped)&quot;] --&amp;gt; B[&quot;Microsoft Platform Crypto Provider -- TPM 2.0 or Pluton -- on-CPU silicon&quot;]
    B --&amp;gt; C[&quot;Microsoft Smart Card KSP -- removable hardware token -- (PIV, CAC, Yubikey)&quot;]
    C --&amp;gt; D[&quot;Third-party HSM KSP -- Thales Luna, Entrust nShield, -- YubiHSM 2, AWS CloudHSM&quot;]
    A -.-&amp;gt; A1[&quot;~10^4 RSA-2048 sign/sec -- FIPS 140-2 L1&quot;]
    B -.-&amp;gt; B1[&quot;~1-10 sign/sec -- TPM vendor cert&quot;]
    C -.-&amp;gt; C1[&quot;~1-5 sign/sec -- card vendor cert&quot;]
    D -.-&amp;gt; D1[&quot;~10^2-10^4 sign/sec -- FIPS 140-2/-3 L3 typical&quot;]
&lt;h3&gt;4.1 The Microsoft Software KSP&lt;/h3&gt;
&lt;p&gt;The default. If you pass &lt;code&gt;NULL&lt;/code&gt; for the provider name in &lt;code&gt;NCryptOpenStorageProvider&lt;/code&gt;, you get this one. It stores per-user private keys at &lt;code&gt;%APPDATA%\Microsoft\Crypto\Keys\&lt;/code&gt; and per-machine keys at &lt;code&gt;%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\SystemKeys\&lt;/code&gt;, with each file-level blob further protected by DPAPI under either the user master key or the LocalSystem (&lt;code&gt;S-1-5-18&lt;/code&gt;) master key. The private-key operations dispatch through LRPC into the LSA key-isolation process so that even with administrator privileges on the machine, naive code-injection into the application&apos;s address space does not yield key bits.&lt;/p&gt;
&lt;p&gt;The Microsoft Software KSP is also the only KSP that runs inside the LSA key-isolation process. Third-party KSPs run in the calling application&apos;s process. That difference matters enormously for the threat model. Microsoft notes this explicitly: third-party KSPs &quot;do not run inside the LSA process&quot; [@learn-microsoft-com-and-retrieval]. If you are a third-party KSP that talks to remote HSM hardware, the isolation comes from the HSM itself, not from any Windows process boundary.&lt;/p&gt;
&lt;h3&gt;4.2 The Microsoft Platform Crypto Provider (TPM and Pluton)&lt;/h3&gt;
&lt;p&gt;The KSP that answers to &lt;code&gt;MS_PLATFORM_KEY_STORAGE_PROVIDER&lt;/code&gt; is the TPM&apos;s face to CNG. When you call &lt;code&gt;NCryptCreatePersistedKey&lt;/code&gt; against it, the TPM 2.0 chip itself [@learn-microsoft-com-tpm-fundamentals] generates the key under the protection of its Storage Root Key. The private bits never leave the chip. The application gets back a handle whose only operations are sign, decrypt, and key derivation -- the private key cannot be exported, and that property is enforced by physics, not by software policy.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The Platform Crypto Provider is the place where CNG stops trusting the operating system and starts trusting a separate piece of silicon. Every TPM-backed key in Windows -- BitLocker&apos;s Volume Master Key wrapping, Windows Hello credentials, AD CS attestation-enrolled machine identities -- enters and exits through this single KSP name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Microsoft Pluton, the security processor that shipped in 2022 on AMD Ryzen 6000, Snapdragon 8cx Gen 3, and Intel Core Ultra Series 2 silicon, is exposed to Windows as a TPM 2.0 device behind the same Platform Crypto Provider name [@learn-microsoft-com-security-processor]. Application code that worked against a discrete TPM works against Pluton with no changes. Pluton&apos;s wins are at the supply-chain layer (no SPI bus to physically tap between the chip and the CPU) and the firmware-update layer (Pluton firmware ships via Windows Update). The Windows-facing API is intentionally identical.&lt;/p&gt;
&lt;h3&gt;4.3 The Microsoft Smart Card KSP&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;MS_SMART_CARD_KEY_STORAGE_PROVIDER&lt;/code&gt; is a single KSP that routes to whichever vendor minidriver claims the inserted card. The minidriver model is Microsoft&apos;s plug-in layer below the KSP layer: smart-card vendors do not write CNG KSPs, they write minidrivers, and Microsoft&apos;s single KSP fans the calls out to them via the APDU protocol. Cards that follow Microsoft&apos;s Generic Identity Device Specification (GIDS) [@learn-microsoft-com-device-specification] work without a vendor minidriver. Cards that do not, including most US federal PIV cards before about 2015, ship vendor-specific minidrivers.&lt;/p&gt;
&lt;p&gt;This is the layer that powers Windows Hello for Business &quot;virtual smart card&quot; credentials, which present a TPM-backed key through the smart-card path because so much enterprise software already knew how to talk to PIV-style cards.&lt;/p&gt;
&lt;h3&gt;4.4 Third-party HSM and security-key KSPs&lt;/h3&gt;
&lt;p&gt;YubiHSM 2, Thales Luna, Entrust nShield, AWS CloudHSM Client for Windows, and various cloud-KMS bridges all ship CNG KSPs. The KSP DLL pretends to be a local provider and proxies operations across whatever transport the device uses -- USB for a YubiHSM, PCIe or TCP for a Luna, HTTPS for a cloud HSM. Latency varies from microseconds for a USB device to a few milliseconds for a network HSM. The application code that calls &lt;code&gt;NCryptSignHash&lt;/code&gt; does not change.&lt;/p&gt;

For an internal Active Directory Certificate Services CA, the KSP choice is the entire trust story. A CA whose root key lives in the Software KSP can have that key extracted by any administrator. A CA whose root lives in a FIPS 140-2 Level 3 HSM KSP requires physical access to the HSM (often with multi-person key ceremonies) to recover the key. The application code in `certutil` is identical in both cases. The audit story is not.
&lt;h2&gt;5. The TPM KSP, attestation, and the hardware boundary&lt;/h2&gt;
&lt;p&gt;A TPM-bound key is a useful key, but a TPM-bound key with an attestation statement is a different kind of asset entirely. The Trusted Platform Module supports a primitive called key attestation: the TPM can sign a statement that says, &quot;this key was generated inside me, I will never let it out, and here is a chain of trust back to my Endorsement Key that proves I am a real TPM made by a real vendor.&quot; A certificate authority that requires this attestation can refuse to issue a certificate for any key that did not come from inside a TPM.&lt;/p&gt;
&lt;p&gt;Active Directory Certificate Services supports exactly this flow as &quot;TPM key attestation&quot; [@learn-microsoft-com-key-attestation]. The flow involves three keys: an Endorsement Key (EK) burned into the TPM at manufacture, an Attestation Identity Key (AIK) derived from the EK and certified by Microsoft or by the enterprise PKI, and the application key being attested. The AIK signs a statement covering the application key&apos;s properties; the CA verifies the AIK certificate chain and the statement, and only then issues a certificate.&lt;/p&gt;

flowchart TD
    EK[&quot;Endorsement Key (EK) -- burned into TPM at manufacture -- vendor cert from Intel/AMD/etc.&quot;]
    AIK[&quot;Attestation Identity Key (AIK) -- generated in TPM, certified by -- Microsoft EK CA or enterprise PKI&quot;]
    APPK[&quot;Application key -- generated in TPM via -- NCryptCreatePersistedKey&quot;]
    STMT[&quot;Attestation statement -- signed by AIK&quot;]
    CA[&quot;Enterprise CA (AD CS) -- verifies AIK chain -- and attestation&quot;]
    CERT[&quot;X.509 certificate -- issued to application key&quot;]&lt;pre&gt;&lt;code&gt;EK --&amp;gt; AIK
AIK --&amp;gt; STMT
APPK --&amp;gt; STMT
STMT --&amp;gt; CA
CA --&amp;gt; CERT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CNG-facing API for this is the property bag on a &lt;code&gt;NCRYPT_KEY_HANDLE&lt;/code&gt;. After creating the key, the application calls &lt;code&gt;NCryptGetProperty&lt;/code&gt; with &lt;code&gt;NCRYPT_KEY_ATTESTATION_PROPERTY&lt;/code&gt; (and friends) to retrieve the attestation blob. The CA receives the blob in the certificate request and validates it against Microsoft&apos;s published EK CA roots. The whole protocol fits inside the standard certificate-enrollment flow.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; A software KSP can promise that a key is non-exportable. A TPM KSP can prove it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Throughput is the price. A typical TPM 2.0 chip performs single-digit RSA-2048 signatures per second. Pluton-based platforms are in the same neighborhood. Any architecture that wants to do a TPM signature on every HTTP request will fall over almost immediately. The TPM is the right home for one signature per session, per boot, or per logon -- not one per packet.Key migration between TPMs is essentially impossible by design. Replace a motherboard, and any keys that were sealed to the old TPM&apos;s Storage Root Key are gone. This is the same property that makes BitLocker safe against motherboard theft (the recovery key, escrowed elsewhere, is the only way back) and the same property that makes TPM-bound device identities a key-management headache during hardware refresh cycles.&lt;/p&gt;
&lt;p&gt;There is a deeper, more philosophical reason to use the TPM that the API does not advertise. Software keys are bounded by the kernel&apos;s process-isolation guarantees. Any kernel-level attacker, any user with &lt;code&gt;SeDebugPrivilege&lt;/code&gt;, or any code injected into &lt;code&gt;lsass.exe&lt;/code&gt; can in principle reach key material. The provably stronger bound -- keys that no OS-level code can ever read -- requires an off-CPU hardware boundary. CNG&apos;s own design notes acknowledge this when they say CNG &quot;is designed to be usable as a component in a FIPS level 2 validated system&quot; [@learn-microsoft-com-cng-features]: software-only isolation maps to FIPS 140-2 Levels 1 and 2; hardware boundaries are required for Level 3 and above.&lt;/p&gt;
&lt;h2&gt;6. FIPS 140 mode, compliance, and the one-bit toggle&lt;/h2&gt;
&lt;p&gt;There is a registry value at &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy\Enabled&lt;/code&gt;. When it is set to 1 (or when the equivalent Group Policy &quot;System cryptography: Use FIPS compliant algorithms for encryption, hashing, and signing&quot; is enabled), Schannel and CNG callers refuse to use algorithms that fall outside the FIPS-approved set. RC4 disappears. MD5 disappears. SHA-1 disappears for new signatures (though not for legacy verification). TLS suites that rely on any of those are removed from the negotiation list.&lt;/p&gt;
&lt;p&gt;The toggle is a runtime gate, not a code path. The underlying modules -- &lt;code&gt;bcryptprimitives.dll&lt;/code&gt; and &lt;code&gt;cng.sys&lt;/code&gt; [@learn-microsoft-com-140-windows11] -- are the same modules either way. They have been submitted to the Cryptographic Module Validation Program [@csrc-nist-gov-modules-search] and validated against the FIPS 140-2 standard [@csrc-nist-gov-2-final]. The toggle simply tells those modules that the calling environment expects FIPS-mode behavior, and the modules then refuse the non-approved algorithms.&lt;/p&gt;

A US federal certification program (Federal Information Processing Standard 140) that subjects a cryptographic module to laboratory testing and NIST review. Validated modules receive a public CMVP certificate. Federal agencies, FedRAMP/CMMC contractors, and most regulated industries can only use validated modules in approved configurations. FIPS 140-2 and the newer FIPS 140-3 differ mainly in test methodology and the standard&apos;s own ISO/IEC alignment.
&lt;p&gt;Two current Windows 11 certificate numbers are worth memorizing. CMVP certificate #4825 covers &lt;code&gt;bcryptprimitives.dll&lt;/code&gt; [@csrc-nist-gov-certificate-4825]. CMVP certificate #4766 covers &lt;code&gt;cng.sys&lt;/code&gt; [@csrc-nist-gov-certificate-4766], the kernel-mode primitives. Both are FIPS 140-2 Level 1 modules with a sunset date of September 21, 2026 under the CMVP&apos;s transition rules. Microsoft maintains the per-version FIPS validation portal for Windows 11 [@learn-microsoft-com-140-windows11], which lists the active certificates per build and the algorithms each one covers.&lt;/p&gt;
&lt;p&gt;The cadence mismatch is the open story here. Windows ships H1 and H2 feature updates roughly every six months. CMVP validation of a new build&apos;s primitives DLL and kernel module typically takes 12 to 24 months. Federal customers, FedRAMP-bound cloud tenants, and CMMC contractors cannot run a Windows build that does not have an active FIPS certificate covering its cryptographic modules. Microsoft submits 140-3 evidence for newer modules, but as of mid-2026 no public 140-3 certificate is visible on CMVP for the &lt;code&gt;bcryptprimitives.dll&lt;/code&gt; shipping in Windows 11 24H2.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Setting &lt;code&gt;FIPSAlgorithmPolicy\Enabled = 1&lt;/code&gt; is necessary for FIPS compliance, but not sufficient. The validated configuration also requires that Windows be a covered build (with an active certificate), that you avoid third-party crypto libraries that have not been validated, and that algorithm choices stay inside the per-certificate Approved Mode list. A Windows version without an active certificate is not in compliance even with the toggle on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The toggle also does not change the SymCrypt implementations. AES-GCM is still AES-GCM. What changes is which APIs the caller is allowed to reach. From the application&apos;s point of view, the symptom of FIPS mode is &lt;code&gt;STATUS_NOT_SUPPORTED&lt;/code&gt; on &lt;code&gt;BCryptOpenAlgorithmProvider(L&quot;RC4&quot;, ...)&lt;/code&gt;. From an auditor&apos;s point of view, the symptom is the absence of any disallowed primitive call in the binary.&lt;/p&gt;
&lt;h2&gt;7. The post-quantum slide: ML-KEM, ML-DSA, and the agility test&lt;/h2&gt;
&lt;p&gt;The piece of CNG that earns its &quot;agility&quot; billing is the post-quantum transition.&lt;/p&gt;
&lt;p&gt;NIST opened the Post-Quantum Cryptography standardization process in 2016 and ran four rounds of public evaluation [@csrc-nist-gov-quantum-cryptography] before issuing the first final standards in August 2024. FIPS 203 standardizes ML-KEM (formerly CRYSTALS-Kyber), a module-lattice key encapsulation mechanism [@nvlpubs-nist-gov-fips-nistfips203pdf]. FIPS 204 standardizes ML-DSA (formerly CRYSTALS-Dilithium), a module-lattice digital signature algorithm [@csrc-nist-gov-204-final]. Microsoft Research had been working on lattice cryptography for years [@microsoft-com-quantum-cryptography], and the public CNG implementations followed quickly: Windows 11 24H2 ships ML-KEM and ML-DSA as first-class CNG algorithms.&lt;/p&gt;
&lt;p&gt;Here is the surprising part: the CNG API surface did not change. Adding ML-KEM was a matter of registering new algorithm identifier strings -- &lt;code&gt;BCRYPT_MLKEM_ALGORITHM&lt;/code&gt;, the parameter sets &lt;code&gt;BCRYPT_MLKEM_PARAMETER_SET_512&lt;/code&gt;, &lt;code&gt;BCRYPT_MLKEM_PARAMETER_SET_768&lt;/code&gt;, &lt;code&gt;BCRYPT_MLKEM_PARAMETER_SET_1024&lt;/code&gt; -- in the CNG algorithm-identifier registry [@learn-microsoft-com-algorithm-identifiers]. The opening dance for an ML-KEM key encapsulation looks exactly like the opening dance for an ECDH key agreement, except for the string.&lt;/p&gt;
&lt;p&gt;{`
// Mirrors the BCrypt pattern shown in the Microsoft sample
// &quot;Using ML-KEM with CNG for Key Exchange&quot;&lt;/p&gt;
&lt;p&gt;const hAlg = BCryptOpenAlgorithmProvider(&quot;ML-KEM&quot;, null, 0);&lt;/p&gt;
&lt;p&gt;const hKeyPair = BCryptGenerateKeyPair(hAlg, 0, 0);
BCryptSetProperty(hKeyPair, &quot;ParameterSetName&quot;, &quot;ML-KEM-768&quot;);
BCryptFinalizeKeyPair(hKeyPair, 0);&lt;/p&gt;
&lt;p&gt;const pubBlob   = BCryptExportKey(hKeyPair, &quot;MLKEMPUBLICBLOB&quot;);&lt;/p&gt;
&lt;p&gt;// Sender side: encapsulate to recipient&apos;s public key
const recipPub  = BCryptImportKeyPair(hAlg, &quot;MLKEMPUBLICBLOB&quot;, pubBlob);
const { ciphertext, sharedSecret: ssA } = BCryptEncapsulate(recipPub);&lt;/p&gt;
&lt;p&gt;// Recipient side: decapsulate with the matching private key
const ssB = BCryptDecapsulate(hKeyPair, ciphertext);&lt;/p&gt;
&lt;p&gt;// ssA === ssB
`}&lt;/p&gt;
&lt;p&gt;That code is structurally identical to a 2007-era ECDH session. The string changes, the blob format changes, and the wire-format sizes change considerably. ML-KEM ciphertexts at the 512, 768, and 1024 parameter sets are 768, 1088, and 1568 bytes respectively, with public keys of 800, 1184, and 1568 bytes per FIPS 203 [@csrc-nist-gov-203-final]. ML-DSA signatures at parameter sets 44, 65, and 87 are 2420, 3309, and 4627 bytes per FIPS 204 [@csrc-nist-gov-204-final]. For comparison, an ECDSA P-256 signature is 64 bytes and an X25519 public key is 32 bytes. The PQC blowup is roughly an order of magnitude, and that has knock-on consequences for every protocol that carries certificates or handshakes on the wire.&lt;/p&gt;

The reason ML-KEM matters before any large quantum computer exists is the harvest-now, decrypt-later attack: an adversary recording today&apos;s TLS sessions can decrypt them years from now if the long-lived key-exchange material was only protected by RSA or ECDH. Long-lived secrets transmitted over the wire today -- medical records, source code, government cables -- have a confidentiality lifetime measured in decades. The motivation for hybrid PQ key exchange is that you cannot un-record traffic.
&lt;p&gt;The wire-format problem is why most TLS-PQ deployments use hybrid groups: classical X25519 combined with ML-KEM-768, with the shared secret derived from both. If either component breaks, the other one still holds. The IETF draft &lt;code&gt;draft-kwiatkowski-tls-ecdhe-mlkem&lt;/code&gt; [@learn-microsoft-com-mlkem-examples] defines the &lt;code&gt;X25519MLKEM768&lt;/code&gt; group with IANA codepoint 0x11EC, and Chrome, Cloudflare, and AWS shipped support in production in 2024. OpenJDK JEP 527 [@openjdk-org-jeps-527] tracks the equivalent work for Java&apos;s TLS stack. Schannel in Windows 11 24H2 can negotiate ML-KEM through CNG, but Microsoft has not publicly committed to a default-on hybrid group at the Schannel layer as of mid-2026.&lt;/p&gt;

On a Windows 11 24H2 machine, the following PowerShell snippet asks CNG for its registered algorithms:&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;[System.Security.Cryptography.CngAlgorithm]::new(&quot;ML-KEM&quot;)
Get-ChildItem &apos;HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\Default\0010&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first line forces a CngAlgorithm lookup. The second walks the configuration store. If the keys &lt;code&gt;ML-KEM&lt;/code&gt; and &lt;code&gt;ML-DSA&lt;/code&gt; appear, your kernel-mode and user-mode primitives are 24H2-current.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The bigger structural lesson is that two decades of &quot;cryptographic agility&quot; claims actually paid off. The PQC transition required a 24H2 update, not a CNG redesign.&lt;/p&gt;
&lt;h2&gt;8. Where CNG actually shows up: TLS, BitLocker, and friends&lt;/h2&gt;
&lt;p&gt;The argument for an OS-level cryptographic API stands or falls on what runs on top of it. Every modern Windows component that touches cryptography is a CNG consumer.&lt;/p&gt;

The Windows implementation of TLS and DTLS, exposed through the SSPI (Security Support Provider Interface). Schannel handles the TLS protocol state machine, certificate validation, and cipher-suite negotiation, then delegates the actual cryptography to BCrypt and NCrypt. The cipher-suite priority list and protocol-version controls are configured per Windows version, often via Group Policy.
&lt;p&gt;&lt;strong&gt;Schannel&lt;/strong&gt;, the Windows TLS stack, sits directly above CNG. The Schannel cipher-suite list is its own per-version object, documented at the Schannel cipher-suites portal [@learn-microsoft-com-in-schannel]. For TLS 1.2 and earlier, the order is administered via the registry key &lt;code&gt;HKLM\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010002&lt;/code&gt; (the &quot;Functions&quot; value) or the Group Policy &quot;SSL Cipher Suite Order.&quot; For TLS 1.3, the three suites (&lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt;, &lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt;, &lt;code&gt;TLS_CHACHA20_POLY1305_SHA256&lt;/code&gt;) are not user-orderable; Schannel hard-codes the priority. TLS 1.0 and TLS 1.1 are off by default in Windows 11 23H2 and later, per Microsoft&apos;s August 2023 deprecation announcement [@techcommunity-microsoft-com-windows-3887947].&lt;/p&gt;

flowchart TD
    App[&quot;Application -- (WinHTTP, HttpClient, browser, ...)&quot;]
    SSPI[&quot;SSPI / CredSSP layer&quot;]
    Schannel[&quot;Schannel -- protocol state machine -- cipher-suite negotiation&quot;]
    BCrypt[&quot;BCrypt -- AES-GCM, SHA-2/3, HKDF, RNG&quot;]
    NCrypt[&quot;NCrypt -- server cert private key sign -- client cert auth&quot;]
    KSP[&quot;KSP (Software / TPM / -- Smart Card / HSM)&quot;]&lt;pre&gt;&lt;code&gt;App --&amp;gt; SSPI
SSPI --&amp;gt; Schannel
Schannel --&amp;gt; BCrypt
Schannel --&amp;gt; NCrypt
NCrypt --&amp;gt; KSP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;BitLocker&lt;/strong&gt; is the canonical NCrypt-and-TPM consumer. The Full Volume Encryption Key (FVEK) is generated and stored encrypted on disk. The Volume Master Key (VMK) wraps the FVEK and is itself wrapped by one or more &quot;protectors&quot;: the TPM, a recovery password, a startup PIN, a USB startup key. The TPM protector is an NCrypt-style operation against the Platform Crypto Provider, sealed to a set of Platform Configuration Register (PCR) measurements that capture the boot state. If anything in the early boot chain changes, the PCRs do not match, the TPM refuses to unwrap the VMK, and BitLocker falls back to recovery.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Authenticode&lt;/strong&gt;, the signature format on Windows binaries, is a NCrypt-driven workflow at signing time and a BCrypt-driven workflow at verification time. The Windows kernel verifies driver signatures, the Windows loader verifies binary signatures, and &lt;code&gt;WinVerifyTrust&lt;/code&gt; exposes the same machinery to applications. The hash algorithm in modern Authenticode is SHA-256, which means every signed executable on the system has a SHA-256 digest computed by BCrypt at some point during validation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Credential Guard&lt;/strong&gt; runs the LSA isolated process (&lt;code&gt;lsaiso.exe&lt;/code&gt;) inside the Virtualization-Based Security trustlet boundary on systems with VBS enabled. Credential Guard does not replace CNG; it relocates the Microsoft Software KSP into a stronger isolation boundary. NTLM password hashes and Kerberos TGT session keys live inside that boundary, accessible only through the standard CNG calls dispatched into the trustlet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Windows Hello for Business&lt;/strong&gt; uses the Platform Crypto Provider as the home for the user&apos;s gesture-protected authentication key. The biometric (or PIN) unlocks a key in the TPM; that key signs an attestation that is consumed by Azure AD or AD FS. The biometric never leaves the device.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DPAPI and DPAPI-NG&lt;/strong&gt; are themselves built on CNG, and they deserve their own section because they are the easiest place to see how the layering pays off.&lt;/p&gt;

Schannel, BitLocker, EFS, Authenticode, Credential Guard, Windows Hello, DPAPI-NG, IPsec, SMB encryption, Kerberos PKINIT -- every modern Windows component is a CNG consumer.
&lt;h2&gt;9. DPAPI-NG: a worked example of the NCrypt model&lt;/h2&gt;
&lt;p&gt;The original Data Protection API (DPAPI), shipped with Windows 2000, was a per-user secret-protection mechanism. An application called &lt;code&gt;CryptProtectData&lt;/code&gt;, passed a blob of secret data, and got back an encrypted blob that only the same user on the same machine could later unwrap. The mechanism was anchored in the user&apos;s logon credentials, with a master key per user and a complex backup mechanism for password resets. It worked. It also locked the secret to a single machine, which became a problem the moment users started living on more than one device.&lt;/p&gt;
&lt;p&gt;DPAPI-NG, introduced in Windows 8 and Windows Server 2012, is the cloud-era rebuild. The CNG DPAPI documentation [@learn-microsoft-com-cng-dpapi] describes the three calls: &lt;code&gt;NCryptCreateProtectionDescriptor&lt;/code&gt;, &lt;code&gt;NCryptProtectSecret&lt;/code&gt;, and &lt;code&gt;NCryptUnprotectSecret&lt;/code&gt;. The protection descriptor is a small string that names who can unwrap the data. Examples include &lt;code&gt;SID=S-1-5-21-...&lt;/code&gt; for an Active Directory user or group, &lt;code&gt;LOCAL=user&lt;/code&gt; for the legacy single-user behavior, &lt;code&gt;WEBCREDENTIALS=...&lt;/code&gt; for a credential vault entry, and combinations connected by &lt;code&gt;AND&lt;/code&gt; or &lt;code&gt;OR&lt;/code&gt; operators.&lt;/p&gt;

flowchart LR
    Plain[&quot;plaintext secret&quot;] --&amp;gt; Protect[&quot;NCryptProtectSecret(descriptor, plain)&quot;]
    Desc[&quot;descriptor: -- SID=group GUID -- OR -- LOCAL=user&quot;] --&amp;gt; Protect
    Protect --&amp;gt; Blob[&quot;opaque blob&quot;]
    Blob --&amp;gt; Unprotect[&quot;NCryptUnprotectSecret(blob)&quot;]
    Unprotect -.-&amp;gt;|&quot;resolves descriptor -- via AD DC backup keys&quot;| AD[&quot;Active Directory DC -- (DPAPI backup keys)&quot;]
    Unprotect --&amp;gt; Out[&quot;plaintext secret -- on any authorized machine&quot;]
&lt;p&gt;The architectural win is that DPAPI-NG is just NCrypt with a particular protection-descriptor schema. Any KSP that can serve the key referenced by the descriptor can satisfy the unwrap. In an Active-Directory-joined environment, the AD domain controller&apos;s DPAPI backup keys allow any machine where the user (or any member of the named group) authenticates to recover the secret. The application that called &lt;code&gt;NCryptProtectSecret&lt;/code&gt; does not need to know about backup keys, replication topology, or recovery flows. It calls NCrypt; the router and the relevant KSP do the rest.&lt;/p&gt;
&lt;p&gt;This is the design payoff of the two-tier model. A new key-management capability (cross-machine recovery via AD-stored backup keys) becomes a new descriptor type, not a new API. The Windows team has used the same descriptor extensibility to add web-credential descriptors, container-bound descriptors, and the descriptors that protect Group Managed Service Account passwords. Each one is a private key-management concern; none of them broke the public API.The DPAPI-NG descriptor language is small enough to read in one sitting and powerful enough to express &quot;any member of this AD group, on any machine where that member can authenticate.&quot; That is the cloud-era access-control story that the original DPAPI never had.&lt;/p&gt;
&lt;h2&gt;10. Engineering takeaways: choosing the right tool&lt;/h2&gt;
&lt;p&gt;The decision tree for CNG usage in production code is short.&lt;/p&gt;

flowchart TD
    Q1{&quot;Need persistent -- private key?&quot;}
    Q1 -- No --&amp;gt; B[&quot;BCrypt -- (ephemeral key, pseudo-handle)&quot;]
    Q1 -- Yes --&amp;gt; Q2{&quot;Threat model?&quot;}
    Q2 -- &quot;Machine identity, -- hardware-rooted&quot; --&amp;gt; P[&quot;Microsoft Platform -- Crypto Provider -- (TPM / Pluton)&quot;]
    Q2 -- &quot;User-bound PKI, -- removable hardware&quot; --&amp;gt; S[&quot;Microsoft Smart Card KSP -- (PIV / virtual smart card)&quot;]
    Q2 -- &quot;High signing rate, -- regulated custody&quot; --&amp;gt; H[&quot;Third-party HSM KSP -- (YubiHSM / Luna / nShield)&quot;]
    Q2 -- &quot;Default, -- portable, fast&quot; --&amp;gt; SW[&quot;Microsoft Software KSP&quot;]
&lt;p&gt;For algorithm choice in mid-2026, the defensible defaults look like this. Symmetric encryption: ChaCha20-Poly1305 or AES-256-GCM. Hashing: SHA-256 or SHA-3 family. Signatures: ECDSA P-256 or P-384 today, with ML-DSA-65 in the back pocket for the inevitable hybrid transition. Key encapsulation: X25519 today, with X25519+ML-KEM-768 hybrid as soon as your peers support it. RSA-2048 only for legacy interoperability. RC4, 3DES, and SHA-1 only behind explicit deprecation policy, and only for verification of historical artifacts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The hardest thing about CNG is not learning the API. It is choosing the right KSP. That single decision -- where the private key actually lives -- determines almost everything about your threat model, your throughput, your compliance posture, and your operational complexity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A few engineering rules survive in any setting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not put persistent keys in BCrypt.&lt;/strong&gt; Every BCrypt key handle dies with the process. The architectural separation exists for a reason. If the key needs to survive a reboot, it belongs in NCrypt under a named KSP.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not assume the Software KSP.&lt;/strong&gt; Code that calls &lt;code&gt;NCryptOpenStorageProvider(NULL)&lt;/code&gt; ends up with whatever the default is. On a server with an HSM KSP configured as the default, this might be what you want; on a developer workstation, it might be the Microsoft Software KSP. Be explicit. Pass the name string. Test the negative case where the KSP you named is not registered.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audit which KSP your certificates actually use.&lt;/strong&gt; A certificate enrolled with the Platform Crypto Provider behaves identically to a certificate enrolled with the Software KSP from &lt;code&gt;certutil&lt;/code&gt;&apos;s point of view. The difference is invisible until you ask. Use &lt;code&gt;certutil -store -v My&lt;/code&gt; to dump certificate properties, and look for the provider field.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Treat FIPS mode as a deployment fact, not a development toggle.&lt;/strong&gt; Code that works fine on a developer workstation can break in surprising ways on a FIPS-enabled production server. Run your CI on a FIPS-toggled image periodically. Catch the &lt;code&gt;STATUS_NOT_SUPPORTED&lt;/code&gt; returns before customers do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watch the PQC roadmap.&lt;/strong&gt; The ML-KEM and ML-DSA primitives are in 24H2. Hybrid TLS in Schannel is not on by default at the OS level as of mid-2026 (the most recent Microsoft public posture in the cipher-suite documentation does not yet list a default-on hybrid group), but downstream protocol updates will come. Code that uses the BCrypt and NCrypt patterns shown here picks up the new algorithms with a string change.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The single most useful CNG diagnostic command on a modern Windows system is &lt;code&gt;certutil -csptest&lt;/code&gt;, which enumerates registered providers and the algorithms each one claims to support. Run it before you suspect a configuration drift, not after.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The story of CNG is the story of two architectural bets that paid off. The first bet was that algorithms would keep arriving, so the API should be a registry of strings rather than a hard-coded set of functions. The second bet was that key storage was a separate concern from algorithm implementation, so the same primitives could run against software, TPM, smart cards, and HSMs without changing the application. In 2007 those bets looked over-engineered. In 2026, with ML-KEM shipping behind the same &lt;code&gt;BCryptEncapsulate&lt;/code&gt; call that an ECDH consumer would have used, they look like exactly the right design.&lt;/p&gt;
&lt;h2&gt;Frequently asked questions&lt;/h2&gt;

No. Microsoft&apos;s BCrypt is the `bcrypt.h` primitives header in CNG, providing AES, SHA, HMAC, RNG, and related primitives. The Provos-Mazieres bcrypt is a password-hashing function based on the Blowfish cipher, with no connection to Windows. The naming collision is unfortunate but firmly entrenched. When in doubt, BCrypt with a capital &quot;B&quot; usually means Microsoft&apos;s CNG header; lowercase bcrypt usually means the password-hashing function.

On Windows, yes. .NET&apos;s `System.Security.Cryptography` namespace wraps CNG directly: `RSACng`, `ECDsaCng`, `AesGcm`, `SHA256.HashData()`, `CngKey`. Go, Rust, and Python bindings exist as third-party crates and packages (the Rust `windows` crate exposes both BCrypt and NCrypt, for example). OpenSSL on Windows does not transparently use CNG; you need the `openssl-cng` provider or direct CNG calls if you want the OS-validated primitives to do the work.

Both can do RSA, ECDSA, and (in 24H2) ML-DSA signatures. The difference is lifetime. BCrypt key handles are ephemeral: they live in your process and disappear when it exits. NCrypt keys are persisted in a KSP and survive process exit, reboots, and (for AD-replicated descriptors via DPAPI-NG) the loss of a single machine. Use BCrypt for one-shot ephemeral operations (signing a single message, deriving a session key); use NCrypt for anything with a certificate attached or anything that has to be around tomorrow.

Possibly, depending on what algorithms it calls. Setting `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy\Enabled = 1` causes CNG to refuse RC4, MD5, SHA-1 for new signatures, and a handful of other non-approved algorithms. Anything that relied on those returns `STATUS_NOT_SUPPORTED`. The fix is to switch to approved algorithms (AES, SHA-2 family, RSA, ECDSA, ML-KEM, ML-DSA), not to disable the toggle. The toggle is also necessary but not sufficient for FIPS compliance: you also need a Windows build with an active CMVP certificate covering the cryptographic modules.

As of mid-2026, the public Schannel documentation does not list a default-on hybrid group like `X25519MLKEM768`. The ML-KEM primitive is in CNG in 24H2, and Schannel can use it through the standard cipher-suite negotiation, but Microsoft has not publicly committed to enabling a hybrid group out of the box at the OS level. Chrome, Cloudflare, and AWS have already shipped hybrid PQ TLS in production at the application layer. Expect Schannel to follow once IETF standardization stabilizes and CMVP validation of the new modules catches up.

For a certificate in the user or machine store, run `certutil -store -v My` (or `My` replaced with the store name) and look at the &quot;Provider&quot; field of each certificate. `Microsoft Software Key Storage Provider` means the key is on disk under `%APPDATA%` or `%ALLUSERSPROFILE%`. `Microsoft Platform Crypto Provider` means the key lives inside the TPM (or Pluton). `Microsoft Smart Card Key Storage Provider` means the key is on a card. Third-party HSM KSPs will show the vendor&apos;s provider name. For a freshly-created key via `NCryptCreatePersistedKey`, the provider name you passed to `NCryptOpenStorageProvider` is the source of truth.

Because private keys do not live in the calling process. For the Microsoft Software KSP, key material lives in the LSA key-isolation process (`lsaiso.exe` under VBS, `lsass.exe` otherwise), and every operation that touches private bits has to cross that process boundary. The cost is around 30 to 100 microseconds per call. That is acceptable for signing or key derivation (operations that happen a handful of times per session); it would be punishing for bulk symmetric encryption. The architectural answer is to keep bulk crypto in BCrypt and let only the persistent-key operations pay the LRPC cost.
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;cng-architecture-bcrypt-ncrypt-ksps-and-windows-crypto&quot; keyTerms={[
  { term: &quot;CAPI (Cryptographic Application Programming Interface)&quot;, definition: &quot;The original Windows cryptographic API (1998-onward). Plug-in unit was the CSP. Superseded by CNG starting in 2007 but still present for backwards compatibility.&quot; },
  { term: &quot;CNG (Cryptography API: Next Generation)&quot;, definition: &quot;The Windows cryptographic API since Vista (2007). Two-tier split: BCrypt for primitives, NCrypt for key storage. The basis for all modern Windows cryptography.&quot; },
  { term: &quot;CSP (Cryptographic Service Provider)&quot;, definition: &quot;The CAPI-era plug-in unit. Monolithic DLL bundling algorithms, key storage, and FIPS posture.&quot; },
  { term: &quot;KSP (Key Storage Provider)&quot;, definition: &quot;The CNG-era plug-in unit for persistent key storage. Microsoft ships four; third parties ship many more. Selected by name string passed to NCryptOpenStorageProvider.&quot; },
  { term: &quot;Microsoft Software Key Storage Provider&quot;, definition: &quot;The default KSP. Stores DPAPI-wrapped keys on disk and dispatches operations through the LSA key-isolation process via LRPC.&quot; },
  { term: &quot;Microsoft Platform Crypto Provider&quot;, definition: &quot;The TPM-and-Pluton-backed KSP. Keys are generated and used inside the TPM chip; private bits never leave the silicon.&quot; },
  { term: &quot;TPM key attestation&quot;, definition: &quot;A three-key chain (EK -&amp;gt; AIK -&amp;gt; application key) that lets a CA verify a key was generated inside a real TPM. Supported by Active Directory Certificate Services since Windows Server 2012 R2.&quot; },
  { term: &quot;FIPS 140&quot;, definition: &quot;US federal certification program for cryptographic modules. Validated modules receive a public CMVP certificate. Windows 11&apos;s bcryptprimitives.dll holds CMVP certificate #4825, cng.sys holds #4766.&quot; },
  { term: &quot;ML-KEM (FIPS 203)&quot;, definition: &quot;Module-Lattice Key Encapsulation Mechanism. The NIST-standardized post-quantum KEM, formerly known as CRYSTALS-Kyber. Shipped in Windows 11 24H2.&quot; },
  { term: &quot;ML-DSA (FIPS 204)&quot;, definition: &quot;Module-Lattice Digital Signature Algorithm. The NIST-standardized post-quantum signature scheme, formerly known as CRYSTALS-Dilithium. Shipped in Windows 11 24H2.&quot; },
  { term: &quot;DPAPI-NG&quot;, definition: &quot;The CNG-era rebuild of the original Data Protection API. Uses NCrypt protection descriptors to bind protected data to AD principals (users, groups, web credentials) rather than to a single machine.&quot; },
  { term: &quot;SymCrypt&quot;, definition: &quot;Microsoft&apos;s open-source cryptographic implementation library. The actual workhorse behind BCrypt and NCrypt since Windows 10 version 1703 (2017).&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>windows</category><category>cryptography</category><category>cng</category><category>tpm</category><category>pqc</category><category>fips</category><category>ksp</category><category>security</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>