# CNG Architecture: BCrypt, NCrypt, KSPs, and How Windows Picks Its Algorithms

> 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.

*Published: 2026-05-16*
*Canonical: https://paragmali.com/blog/cng-architecture-bcrypt-ncrypt-ksps-and-how-windows-picks-it*
*License: CC BY 4.0 - https://creativecommons.org/licenses/by/4.0/*

---
<TLDR>
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.
</TLDR>

## 1. From CAPI to CNG: why Microsoft started over

In the late 1990s, Microsoft shipped its first general cryptographic API. The original [Cryptographic Service Providers (CAPI) model](https://learn.microsoft.com/en-us/windows/win32/seccrypto/cryptographic-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.

Then the algorithms changed.

AES was standardized in 2001, after CAPI's design was already frozen. Microsoft retrofitted AES into the original architecture by shipping the [Microsoft Enhanced RSA and AES Cryptographic Provider](https://learn.microsoft.com/en-us/windows/win32/seccrypto/microsoft-enhanced-rsa-and-aes-cryptographic-provider) as a separate CSP, sitting alongside the original Microsoft Base Cryptographic Provider. Elliptic-curve cryptography was even more awkward: CAPI'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.

<Sidenote>Microsoft was not alone. The same era produced Intel's [Common Data Security Architecture (CDSA)](https://en.wikipedia.org/wiki/OS/2) and several short-lived crypto frameworks for OS/2 and other platforms. Most of them disappeared. CAPI's longevity owed more to Windows market share than to its design.</Sidenote>

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](https://learn.microsoft.com/en-us/windows/win32/seccng/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.

<Definition term="Cryptography API: Next Generation (CNG)">
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.
</Definition>

<Definition term="Cryptographic Service Provider (CSP)">
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.
</Definition>

The three design pillars Microsoft committed to in the CNG portal documentation were modularity, [cryptographic agility, and FIPS-compliance readiness](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-features). All three would matter twenty years later when post-quantum cryptography arrived without warning the protocol authors. We will get to that.

<Aside label="A note on names">
Throughout this article, "BCrypt" refers to Microsoft'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 ("bcrypt"). The naming collision is unfortunate but firmly entrenched in Windows.
</Aside>

## 2. BCrypt: the symmetric stack and the ephemeral key

Open a Visual Studio project, include `<bcrypt.h>`, link `bcrypt.lib`, 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](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-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](https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final). Ephemeral asymmetric operations -- RSA encrypt, ECDSA sign, ECDH key agreement -- on key handles that vanish when the process exits.

The canonical BCrypt opening dance is four calls.

<RunnableCode lang="js" title="BCrypt opening dance, conceptually">{`
// Pseudocode mirroring the BCryptOpenAlgorithmProvider flow.
// In real C: NTSTATUS values, BCRYPT_ALG_HANDLE, etc.

const algId       = "AES";           // wide string
const impl        = null;            // null -> walk the priority list
const flags       = 0;

const hAlg        = BCryptOpenAlgorithmProvider(algId, impl, flags);
BCryptSetProperty(hAlg, "ChainingMode", "ChainingModeGCM");

const hKey        = BCryptGenerateSymmetricKey(hAlg, keyBytes);
const ciphertext  = BCryptEncrypt(hKey, plaintext, authInfo);

BCryptDestroyKey(hKey);
BCryptCloseAlgorithmProvider(hAlg, 0);
`}</RunnableCode>

The interesting parameter is `impl`. When it is `NULL`, [`BCryptOpenAlgorithmProvider` "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"](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider). That sentence is the whole story of CNG provider priority in nineteen words.

Algorithm identifiers are wide strings. `L"AES"`, `L"SHA256"`, `L"RSA"`, `L"ML-KEM"`, `L"ML-DSA"`, `L"CHACHA20_POLY1305"`, `L"CSHAKE128"`. Each string is registered in CNG's configuration store under `HKLM\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\`, 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.

> **Note:** The algorithm-identifier string is the seam where cryptographic agility lives. As long as your protocol can encode "use whatever the spec calls AES-256-GCM," 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.

Underneath the API is a single implementation library. [Microsoft's SymCrypt](https://github.com/microsoft/SymCrypt) has been the actual workhorse since Windows 10 version 1703: "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." 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.

<MarginNote>SymCrypt'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.</MarginNote>

BCrypt keys are ephemeral by construction. A `BCRYPT_KEY_HANDLE` 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.

That distinction is the first thing developers get wrong when they meet CNG. The second thing they get wrong is forgetting that BCrypt's GCM API does not allocate nonces for you. The [NIST SP 800-38D specification of Galois/Counter Mode](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf) 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.

> **Note:** First, **GCM nonce reuse**: `BCryptEncrypt` with `BCRYPT_CHAIN_MODE_GCM` accepts whatever 12 bytes you hand it. Counter or random, but never twice. Second, **algorithm string drift**: `BCRYPT_SHA256_ALGORITHM` is the macro for `L"SHA256"`. `L"SHA-256"` returns `STATUS_NOT_FOUND`. Third, **kernel-mode pseudo-handles**: the convenient `BCRYPT_AES_ALG_HANDLE` shortcut is [user-mode only per the BCryptOpenAlgorithmProvider remarks](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider); kernel drivers must use real handles.

Windows 10 added pseudo-handles -- pre-baked handle constants like `BCRYPT_AES_ALG_HANDLE` and `BCRYPT_SHA256_ALG_HANDLE` -- that skip the provider lookup for the built-in algorithms. The 24H2 release extended that list to include `BCRYPT_MLKEM_ALG_HANDLE` and the cSHAKE handles. Microsoft now [recommends pseudo-handles over `BCryptOpenAlgorithmProvider` for new code](https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-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.

That covers the primitives. Now we need a place to keep the keys.

## 3. NCrypt: where the long-lived secrets live

The `ncrypt.h` header opens a different door. Every function in [the NCrypt API surface](https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/) -- `NCryptOpenStorageProvider`, `NCryptCreatePersistedKey`, `NCryptOpenKey`, `NCryptSignHash`, `NCryptDecrypt`, `NCryptKeyDerivation`, `NCryptExportKey`, `NCryptProtectSecret` -- begins by routing the call through `ncrypt.dll`, which acts as a router rather than an implementation. The router decides which Key Storage Provider handles the operation and forwards the call.

That routing layer is the architectural distinction Microsoft has insisted on for two decades. Microsoft's [Key Storage and Retrieval documentation](https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-and-retrieval) describes it like this: the NCrypt router "conceals details, such as key isolation, from both the application and the storage provider itself." Translation: the application calls `NCryptSignHash` and gets back a signature. It does not know -- and should not need to know -- whether the key lives in `%APPDATA%`, 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.

<Definition term="Key Storage Provider (KSP)">
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`.
</Definition>

The mechanical flow for creating a persisted key looks like this.

<Mermaid caption="An NCrypt key creation call traverses ncrypt.dll, the registered KSP, and (for the Microsoft Software KSP) the LSA key-isolation process before touching disk.">
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\

    App->>Router: NCryptOpenStorageProvider("Microsoft Software Key Storage Provider")
    Router-->>App: hProvider
    App->>Router: NCryptCreatePersistedKey(hProvider, "RSA", "MyKey", 2048, ...)
    Router->>KSP: dispatch via registered KSP entry points
    KSP->>LSA: LRPC: generate key, return handle
    LSA->>Disk: write DPAPI-wrapped private blob
    LSA-->>KSP: ok
    KSP-->>Router: hKey
    Router-->>App: hKey
    App->>Router: NCryptSignHash(hKey, digest)
    Router->>KSP: forward
    KSP->>LSA: LRPC: sign with isolated key
    LSA-->>KSP: signature
    KSP-->>Router: signature
    Router-->>App: signature
</Mermaid>

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'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.

<Sidenote>The LSA key-isolation process is `lsaiso.exe` on systems with Credential Guard enabled, hosted inside the Virtualization-Based Security (VBS) trustlet boundary. On systems without VBS, the role is played by `lsass.exe` itself. Either way, key material does not enter the application's address space.</Sidenote>

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](https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-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's repertoire.

The split between BCrypt and NCrypt is sometimes confusing because there is overlap. You can sign with BCrypt's `BCryptSignHash` if you generated an ephemeral key pair. You can also sign with NCrypt's `NCryptSignHash` 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.

> **Note:** The four Microsoft KSP name strings are `MS_KEY_STORAGE_PROVIDER` (Software), `MS_PLATFORM_KEY_STORAGE_PROVIDER` (TPM/Pluton), `MS_SMART_CARD_KEY_STORAGE_PROVIDER`, and `MS_NGC_KEY_STORAGE_PROVIDER` (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 "why is my key on disk instead of in the TPM" incident reports.

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.

## 4. The KSP model: one API, many places to keep keys

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's API. The registry entry under `HKLM\SOFTWARE\Microsoft\Cryptography\Providers\Microsoft Software Key Storage Provider` (and its siblings) tells `ncrypt.dll` 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.

The platform comes with four. They sit on a spectrum from "your operating system is the entire trust boundary" to "the keys live on a separate piece of silicon and only signatures come back."

<Mermaid caption="The KSP plug-in spectrum from software-only to dedicated hardware. Each tier trades throughput for isolation strength.">
flowchart LR
    A["Microsoft Software KSP — private keys on disk — (DPAPI-wrapped)"] --> B["Microsoft Platform Crypto Provider — TPM 2.0 or Pluton — on-CPU silicon"]
    B --> C["Microsoft Smart Card KSP — removable hardware token — (PIV, CAC, Yubikey)"]
    C --> D["Third-party HSM KSP — Thales Luna, Entrust nShield, — YubiHSM 2, AWS CloudHSM"]
    A -.-> A1["~10^4 RSA-2048 sign/sec — FIPS 140-2 L1"]
    B -.-> B1["~1-10 sign/sec — TPM vendor cert"]
    C -.-> C1["~1-5 sign/sec — card vendor cert"]
    D -.-> D1["~10^2-10^4 sign/sec — FIPS 140-2/-3 L3 typical"]
</Mermaid>

### 4.1 The Microsoft Software KSP

The default. If you pass `NULL` for the provider name in `NCryptOpenStorageProvider`, you get this one. It stores per-user private keys at `%APPDATA%\Microsoft\Crypto\Keys\` and per-machine keys at `%ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\SystemKeys\`, with each file-level blob further protected by DPAPI under either the user master key or the LocalSystem (`S-1-5-18`) 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's address space does not yield key bits.

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's process. That difference matters enormously for the threat model. Microsoft notes this explicitly: [third-party KSPs "do not run inside the LSA process"](https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-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.

### 4.2 The Microsoft Platform Crypto Provider (TPM and Pluton)

The KSP that answers to `MS_PLATFORM_KEY_STORAGE_PROVIDER` is the TPM's face to CNG. When you call `NCryptCreatePersistedKey` against it, the [TPM 2.0 chip itself](https://learn.microsoft.com/en-us/windows/security/hardware-security/tpm/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.

> **Key idea:** 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's Volume Master Key wrapping, Windows Hello credentials, AD CS attestation-enrolled machine identities -- enters and exits through this single KSP name.

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](https://learn.microsoft.com/en-us/windows/security/hardware-security/pluton/microsoft-pluton-security-processor). Application code that worked against a discrete TPM works against Pluton with no changes. Pluton'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.

### 4.3 The Microsoft Smart Card KSP

`MS_SMART_CARD_KEY_STORAGE_PROVIDER` is a single KSP that routes to whichever vendor minidriver claims the inserted card. The minidriver model is Microsoft's plug-in layer below the KSP layer: smart-card vendors do not write CNG KSPs, they write minidrivers, and Microsoft's single KSP fans the calls out to them via the APDU protocol. Cards that follow Microsoft's [Generic Identity Device Specification (GIDS)](https://learn.microsoft.com/en-us/windows-hardware/drivers/smartcard/generic-identity-device-specification) work without a vendor minidriver. Cards that do not, including most US federal PIV cards before about 2015, ship vendor-specific minidrivers.

This is the layer that powers Windows Hello for Business "virtual smart card" 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.

### 4.4 Third-party HSM and security-key KSPs

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 `NCryptSignHash` does not change.

<Aside label="Why this matters for certification authorities">
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.
</Aside>

## 5. The TPM KSP, attestation, and the hardware boundary

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, "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." A certificate authority that requires this attestation can refuse to issue a certificate for any key that did not come from inside a TPM.

[Active Directory Certificate Services supports exactly this flow as "TPM key attestation"](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/tpm-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's properties; the CA verifies the AIK certificate chain and the statement, and only then issues a certificate.

<Mermaid caption="TPM key attestation: a three-key chain from the silicon-burned Endorsement Key, through the Attestation Identity Key, to the freshly generated application key. The CA only issues a certificate after verifying the chain.">
flowchart TD
    EK["Endorsement Key (EK) — burned into TPM at manufacture — vendor cert from Intel/AMD/etc."]
    AIK["Attestation Identity Key (AIK) — generated in TPM, certified by — Microsoft EK CA or enterprise PKI"]
    APPK["Application key — generated in TPM via — NCryptCreatePersistedKey"]
    STMT["Attestation statement — signed by AIK"]
    CA["Enterprise CA (AD CS) — verifies AIK chain — and attestation"]
    CERT["X.509 certificate — issued to application key"]

    EK --> AIK
    AIK --> STMT
    APPK --> STMT
    STMT --> CA
    CA --> CERT
</Mermaid>

The CNG-facing API for this is the property bag on a `NCRYPT_KEY_HANDLE`. After creating the key, the application calls `NCryptGetProperty` with `NCRYPT_KEY_ATTESTATION_PROPERTY` (and friends) to retrieve the attestation blob. The CA receives the blob in the certificate request and validates it against Microsoft's published EK CA roots. The whole protocol fits inside the standard certificate-enrollment flow.

> **Key idea:** A software KSP can promise that a key is non-exportable. A TPM KSP can prove it.

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.

<Sidenote>Key migration between TPMs is essentially impossible by design. Replace a motherboard, and any keys that were sealed to the old TPM'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.</Sidenote>

There is a deeper, more philosophical reason to use the TPM that the API does not advertise. Software keys are bounded by the kernel's process-isolation guarantees. Any kernel-level attacker, any user with `SeDebugPrivilege`, or any code injected into `lsass.exe` 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's own design notes acknowledge this when they say [CNG "is designed to be usable as a component in a FIPS level 2 validated system"](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-features): software-only isolation maps to FIPS 140-2 Levels 1 and 2; hardware boundaries are required for Level 3 and above.

## 6. FIPS 140 mode, compliance, and the one-bit toggle

There is a registry value at `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy\Enabled`. When it is set to 1 (or when the equivalent Group Policy "System cryptography: Use FIPS compliant algorithms for encryption, hashing, and signing" 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.

The toggle is a runtime gate, not a code path. The underlying modules -- [`bcryptprimitives.dll` and `cng.sys`](https://learn.microsoft.com/en-us/windows/security/security-foundations/certification/validations/fips-140-windows11) -- are the same modules either way. They have been [submitted to the Cryptographic Module Validation Program](https://csrc.nist.gov/projects/cryptographic-module-validation-program/validated-modules/search) and validated against [the FIPS 140-2 standard](https://csrc.nist.gov/publications/detail/fips/140/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.

<Definition term="FIPS 140 validation">
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's own ISO/IEC alignment.
</Definition>

Two current Windows 11 certificate numbers are worth memorizing. [CMVP certificate #4825 covers `bcryptprimitives.dll`](https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/4825). [CMVP certificate #4766 covers `cng.sys`](https://csrc.nist.gov/projects/cryptographic-module-validation-program/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's transition rules. Microsoft maintains [the per-version FIPS validation portal for Windows 11](https://learn.microsoft.com/en-us/windows/security/security-foundations/certification/validations/fips-140-windows11), which lists the active certificates per build and the algorithms each one covers.

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'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 `bcryptprimitives.dll` shipping in Windows 11 24H2.

> **Note:** Setting `FIPSAlgorithmPolicy\Enabled = 1` 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.

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's point of view, the symptom of FIPS mode is `STATUS_NOT_SUPPORTED` on `BCryptOpenAlgorithmProvider(L"RC4", ...)`. From an auditor's point of view, the symptom is the absence of any disallowed primitive call in the binary.

## 7. The post-quantum slide: ML-KEM, ML-DSA, and the agility test

The piece of CNG that earns its "agility" billing is the post-quantum transition.

NIST [opened the Post-Quantum Cryptography standardization process in 2016 and ran four rounds of public evaluation](https://csrc.nist.gov/projects/post-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](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.pdf). [FIPS 204 standardizes ML-DSA (formerly CRYSTALS-Dilithium), a module-lattice digital signature algorithm](https://csrc.nist.gov/pubs/fips/204/final). [Microsoft Research had been working on lattice cryptography for years](https://www.microsoft.com/en-us/research/project/post-quantum-cryptography/), and the public CNG implementations followed quickly: Windows 11 24H2 ships ML-KEM and ML-DSA as first-class CNG algorithms.

Here is the surprising part: the CNG API surface did not change. Adding ML-KEM was a matter of registering new algorithm identifier strings -- `BCRYPT_MLKEM_ALGORITHM`, the parameter sets `BCRYPT_MLKEM_PARAMETER_SET_512`, `BCRYPT_MLKEM_PARAMETER_SET_768`, `BCRYPT_MLKEM_PARAMETER_SET_1024` -- in [the CNG algorithm-identifier registry](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-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.

<RunnableCode lang="js" title="ML-KEM-768 with the BCrypt pattern (pseudocode)">{`
// Mirrors the BCrypt pattern shown in the Microsoft sample
// "Using ML-KEM with CNG for Key Exchange"

const hAlg = BCryptOpenAlgorithmProvider("ML-KEM", null, 0);

const hKeyPair = BCryptGenerateKeyPair(hAlg, 0, 0);
BCryptSetProperty(hKeyPair, "ParameterSetName", "ML-KEM-768");
BCryptFinalizeKeyPair(hKeyPair, 0);

const pubBlob   = BCryptExportKey(hKeyPair, "MLKEMPUBLICBLOB");

// Sender side: encapsulate to recipient's public key
const recipPub  = BCryptImportKeyPair(hAlg, "MLKEMPUBLICBLOB", pubBlob);
const { ciphertext, sharedSecret: ssA } = BCryptEncapsulate(recipPub);

// Recipient side: decapsulate with the matching private key
const ssB = BCryptDecapsulate(hKeyPair, ciphertext);

// ssA === ssB
`}</RunnableCode>

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](https://csrc.nist.gov/pubs/fips/203/final). ML-DSA signatures at parameter sets 44, 65, and 87 are 2420, 3309, and 4627 bytes per [FIPS 204](https://csrc.nist.gov/pubs/fips/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.

<Aside label="The harvest-now, decrypt-later threat">
The reason ML-KEM matters before any large quantum computer exists is the harvest-now, decrypt-later attack: an adversary recording today'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.
</Aside>

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 [`draft-kwiatkowski-tls-ecdhe-mlkem`](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-mlkem-examples) defines the `X25519MLKEM768` group with IANA codepoint 0x11EC, and Chrome, Cloudflare, and AWS shipped support in production in 2024. [OpenJDK JEP 527](https://openjdk.org/jeps/527) tracks the equivalent work for Java'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.

<Spoiler kind="hint" label="Enumerate the CNG algorithms your system supports">
On a Windows 11 24H2 machine, the following PowerShell snippet asks CNG for its registered algorithms:

```powershell
[System.Security.Cryptography.CngAlgorithm]::new("ML-KEM")
Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\Default\0010'
```

The first line forces a CngAlgorithm lookup. The second walks the configuration store. If the keys `ML-KEM` and `ML-DSA` appear, your kernel-mode and user-mode primitives are 24H2-current.
</Spoiler>

The bigger structural lesson is that two decades of "cryptographic agility" claims actually paid off. The PQC transition required a 24H2 update, not a CNG redesign.

## 8. Where CNG actually shows up: TLS, BitLocker, and friends

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.

<Definition term="Schannel">
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.
</Definition>

**Schannel**, 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](https://learn.microsoft.com/en-us/windows/win32/secauthn/cipher-suites-in-schannel). For TLS 1.2 and earlier, the order is administered via the registry key `HKLM\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010002` (the "Functions" value) or the Group Policy "SSL Cipher Suite Order." For TLS 1.3, the three suites (`TLS_AES_256_GCM_SHA384`, `TLS_AES_128_GCM_SHA256`, `TLS_CHACHA20_POLY1305_SHA256`) 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's August 2023 deprecation announcement](https://techcommunity.microsoft.com/blog/windows-itpro-blog/tls-1-0-and-tls-1-1-soon-to-be-disabled-in-windows/3887947).

<Mermaid caption="The Windows TLS stack as a CNG consumer. Schannel handles the protocol state machine and certificate selection; the primitives come from BCrypt, the long-lived keys from NCrypt.">
flowchart TD
    App["Application — (WinHTTP, HttpClient, browser, ...)"]
    SSPI["SSPI / CredSSP layer"]
    Schannel["Schannel — protocol state machine — cipher-suite negotiation"]
    BCrypt["BCrypt — AES-GCM, SHA-2/3, HKDF, RNG"]
    NCrypt["NCrypt — server cert private key sign — client cert auth"]
    KSP["KSP (Software / TPM / — Smart Card / HSM)"]

    App --> SSPI
    SSPI --> Schannel
    Schannel --> BCrypt
    Schannel --> NCrypt
    NCrypt --> KSP
</Mermaid>

**BitLocker** 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 "protectors": 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.

**Authenticode**, 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 `WinVerifyTrust` 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.

**Credential Guard** runs the LSA isolated process (`lsaiso.exe`) 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.

**Windows Hello for Business** uses the Platform Crypto Provider as the home for the user'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.

**DPAPI and DPAPI-NG** are themselves built on CNG, and they deserve their own section because they are the easiest place to see how the layering pays off.

<PullQuote>
"Schannel, BitLocker, EFS, Authenticode, Credential Guard, Windows Hello, DPAPI-NG, IPsec, SMB encryption, Kerberos PKINIT -- every modern Windows component is a CNG consumer."
</PullQuote>

## 9. DPAPI-NG: a worked example of the NCrypt model

The original Data Protection API (DPAPI), shipped with Windows 2000, was a per-user secret-protection mechanism. An application called `CryptProtectData`, 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'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.

DPAPI-NG, introduced in Windows 8 and Windows Server 2012, is the cloud-era rebuild. The [CNG DPAPI documentation](https://learn.microsoft.com/en-us/windows/win32/seccng/cng-dpapi) describes the three calls: `NCryptCreateProtectionDescriptor`, `NCryptProtectSecret`, and `NCryptUnprotectSecret`. The protection descriptor is a small string that names who can unwrap the data. Examples include `SID=S-1-5-21-...` for an Active Directory user or group, `LOCAL=user` for the legacy single-user behavior, `WEBCREDENTIALS=...` for a credential vault entry, and combinations connected by `AND` or `OR` operators.

<Mermaid caption="DPAPI-NG decouples a protected secret from a single machine. The descriptor names a principal (user, group, web credential), and the unwrap operation uses CNG-resolved keys for that principal -- backed by the AD DC's DPAPI backup keys for cross-machine recovery.">
flowchart LR
    Plain["plaintext secret"] --> Protect["NCryptProtectSecret(descriptor, plain)"]
    Desc["descriptor: — SID=group GUID — OR — LOCAL=user"] --> Protect
    Protect --> Blob["opaque blob"]
    Blob --> Unprotect["NCryptUnprotectSecret(blob)"]
    Unprotect -.->|"resolves descriptor — via AD DC backup keys"| AD["Active Directory DC — (DPAPI backup keys)"]
    Unprotect --> Out["plaintext secret — on any authorized machine"]
</Mermaid>

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'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 `NCryptProtectSecret` 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.

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.

<Sidenote>The DPAPI-NG descriptor language is small enough to read in one sitting and powerful enough to express "any member of this AD group, on any machine where that member can authenticate." That is the cloud-era access-control story that the original DPAPI never had.</Sidenote>

## 10. Engineering takeaways: choosing the right tool

The decision tree for CNG usage in production code is short.

<Mermaid caption="A decision guide for picking the right CNG surface and the right KSP for a given problem.">
flowchart TD
    Q1&#123;"Need persistent — private key?"&#125;
    Q1 -- No --> B["BCrypt — (ephemeral key, pseudo-handle)"]
    Q1 -- Yes --> Q2&#123,"Threat model?"&#125,
    Q2 -- "Machine identity, — hardware-rooted" --> P["Microsoft Platform — Crypto Provider — (TPM / Pluton)"]
    Q2 -- "User-bound PKI, — removable hardware" --> S["Microsoft Smart Card KSP — (PIV / virtual smart card)"]
    Q2 -- "High signing rate, — regulated custody" --> H["Third-party HSM KSP — (YubiHSM / Luna / nShield)"]
    Q2 -- "Default, — portable, fast" --> SW["Microsoft Software KSP"]
</Mermaid>

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.

> **Key idea:** 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.

A few engineering rules survive in any setting.

**Do not put persistent keys in BCrypt.** 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.

**Do not assume the Software KSP.** Code that calls `NCryptOpenStorageProvider(NULL)` 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.

**Audit which KSP your certificates actually use.** A certificate enrolled with the Platform Crypto Provider behaves identically to a certificate enrolled with the Software KSP from `certutil`'s point of view. The difference is invisible until you ask. Use `certutil -store -v My` to dump certificate properties, and look for the provider field.

**Treat FIPS mode as a deployment fact, not a development toggle.** 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 `STATUS_NOT_SUPPORTED` returns before customers do.

**Watch the PQC roadmap.** 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.

> **Note:** The single most useful CNG diagnostic command on a modern Windows system is `certutil -csptest`, which enumerates registered providers and the algorithms each one claims to support. Run it before you suspect a configuration drift, not after.

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 `BCryptEncapsulate` call that an ECDH consumer would have used, they look like exactly the right design.

## Frequently asked questions

<FAQ title="Frequently asked questions about CNG">

<FAQItem question="Is CNG's BCrypt the same thing as the bcrypt password-hashing function?">
No. Microsoft'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 "B" usually means Microsoft's CNG header; lowercase bcrypt usually means the password-hashing function.
</FAQItem>

<FAQItem question="Can I use CNG from .NET, Go, Rust, or Python?">
On Windows, yes. .NET'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.
</FAQItem>

<FAQItem question="What is the difference between BCrypt and NCrypt for asymmetric keys?">
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.
</FAQItem>

<FAQItem question="If I enable FIPS mode, will my application break?">
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.
</FAQItem>

<FAQItem question="When does Windows 11 add hybrid PQ TLS by default?">
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.
</FAQItem>

<FAQItem question="How can I tell whether a private key is in the TPM or on disk?">
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 "Provider" 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's provider name. For a freshly-created key via `NCryptCreatePersistedKey`, the provider name you passed to `NCryptOpenStorageProvider` is the source of truth.
</FAQItem>

<FAQItem question="Why does NCrypt have an LRPC hop on every call?">
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.
</FAQItem>

</FAQ>

<StudyGuide slug="cng-architecture-bcrypt-ncrypt-ksps-and-windows-crypto" keyTerms={[
  { term: "CAPI (Cryptographic Application Programming Interface)", definition: "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." },
  { term: "CNG (Cryptography API: Next Generation)", definition: "The Windows cryptographic API since Vista (2007). Two-tier split: BCrypt for primitives, NCrypt for key storage. The basis for all modern Windows cryptography." },
  { term: "CSP (Cryptographic Service Provider)", definition: "The CAPI-era plug-in unit. Monolithic DLL bundling algorithms, key storage, and FIPS posture." },
  { term: "KSP (Key Storage Provider)", definition: "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." },
  { term: "Microsoft Software Key Storage Provider", definition: "The default KSP. Stores DPAPI-wrapped keys on disk and dispatches operations through the LSA key-isolation process via LRPC." },
  { term: "Microsoft Platform Crypto Provider", definition: "The TPM-and-Pluton-backed KSP. Keys are generated and used inside the TPM chip; private bits never leave the silicon." },
  { term: "TPM key attestation", definition: "A three-key chain (EK -> AIK -> 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." },
  { term: "FIPS 140", definition: "US federal certification program for cryptographic modules. Validated modules receive a public CMVP certificate. Windows 11's bcryptprimitives.dll holds CMVP certificate #4825, cng.sys holds #4766." },
  { term: "ML-KEM (FIPS 203)", definition: "Module-Lattice Key Encapsulation Mechanism. The NIST-standardized post-quantum KEM, formerly known as CRYSTALS-Kyber. Shipped in Windows 11 24H2." },
  { term: "ML-DSA (FIPS 204)", definition: "Module-Lattice Digital Signature Algorithm. The NIST-standardized post-quantum signature scheme, formerly known as CRYSTALS-Dilithium. Shipped in Windows 11 24H2." },
  { term: "DPAPI-NG", definition: "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." },
  { term: "SymCrypt", definition: "Microsoft's open-source cryptographic implementation library. The actual workhorse behind BCrypt and NCrypt since Windows 10 version 1703 (2017)." }
]} />
