Why use CNG instead of CryptoAPI to store keys
In the last posts, I talked a bit about using CryptoAPI and CryptoNG (CNG) to manage encryption keys, and how using CNG sometimes requires some extra work. That begs the question: is that extra work justified? If CryptoAPI is easier to use, why not just use that?
Obviously, one good reason to prefer CNG is that CryptoAPI is deprecated.
Another reason has to do with how CNG and CryptoAPI protect encryption keys: The two APIs have a very different architecture and this difference has a profound impact on how difficult (or easy) it is for a bad actor to access those keys.
To see what that means in practice, let’s use Process Monitor to see how CryptoAPI and CNG store their keys.
Tracing CryptoAPI key access
First, we need a key and store it using CryptoAPI. An easy way to do this is to create a new self-signed certificate and use
the Microsoft Base Cryptographic Provider v1.0
, which is one of the CryptoAPI
cryptographic service providers.
New-SelfSignedCertificate `
-KeyUsage DigitalSignature `
-FriendlyName "Sample CryptoAPI key" `
-KeyExportPolicy NonExportable `
-Subject "Sample CryptoAPI key" `
-CertStoreLocation "cert:\CurrentUser\My" `
-Provider "Microsoft Base Cryptographic Provider v1.0" `
-KeySpec Signature
CryptoAPI stores RSA keys
in %APPDATA%\Microsoft\Crypto\RSA\[User-SID]\
, but these files are encrypted, so they need to be read via CryptoAPI.
To see how CryptoAPI accesses the stored keys, let’s use Process Monitor to see when and how it accesses files in
%APPDATA%\Microsoft\Crypto\RSA\[User-SID]\
. To trigger a key access, we use a little .NET test program
(CryptoApiNetFx.exe
) that uses our self-signed certificate to sign a piece of data.
As expected, we see several CreateFile
calls that access files in %APPDATA%\Microsoft\Crypto\RSA\[User-SID]\
.
The calls originate from CryptoApiNetFx.exe
and if we look at the stack trace, we can see how the CLR (clr.dll
)
calls into CryptoAPI (cryptsp.dll
, rsaenh.dll
) which then makes a system call to access the file:
This stack trace isn’t particularly exciting – but the key thing to notice is: Our process directly accesses the stored keys. As a result, the private key ends up being loaded into our address space. If the key is marked as non-exportable, CryptoAPI won’t allow us to access the private key – but because it’s in our process address space, we can access it anyway as explained in this whitepaper.
The takeaway here is that CryptoAPI doesn’t provide particularly strong protections for stored keys: For a bad actor to exfiltrate a user’s private key, all they need to do is run a process as that user and extract the private key from memory.
Let’s see how CNG compares.
Tracing CNG key access
First, we create another self-signed certificate, this time using the Microsoft Software Key Storage Provider
which
is a CNG key storage provider:
New-SelfSignedCertificate `
-KeyUsage DigitalSignature `
-FriendlyName "Sample CNG key" `
-Subject "Sample CNG key" `
-KeyExportPolicy NonExportable `
-CertStoreLocation "cert:\CurrentUser\My" `
-Provider "Microsoft Software Key Storage Provider" `
-KeyAlgorithm RSA `
-KeyLength 2048
The Microsoft Software Key Storage Provider
stores its keys
in %APPDATA%\Microsoft\Crypto\Keys
. Following the same approach as before, let’s use a test program and
Process Monitor to see how our key is being accessed.
First thing to notice in this Process Monitor trace is that the file isn’t accessed by CryptoApiNetFx.exe
, but by LSASS.
If we look at the stack trace, we can also notice that the file accesses are made by keyiso.dll
, which is servicing an LRPC (rpcrt4.dll
):
keyiso.dll
is the CNG key isolation service:
The CNG key isolation service is hosted in the LSA process. The service provides key process isolation to private keys and associated cryptographic operations as required by the Common Criteria. The service stores and uses long-lived keys in a secure process complying with Common Criteria requirements.
To access a CNG key, our process has to make an LRPC call to LSASS – and that’s important: LSASS is not only a
different process, it also runs as a different user – so there is a security boundary that separates our process
(CryptoApiNetFx.exe
) from the private key. If the CNG key is marked as non-exportable, LSASS won’t give us access
to it and it’ll never be loaded into the address space of our process.
For a bad actor to exfiltrate a private key from CNG, they have to compromise LSASS. That’s possible, but
not exactly easy as LSASS runs as SYSTEM
.
LSA protection
If you hold the debug privilege, you can attach a debugger (or mimikatz) to LSASS and thus undermine the extra protection that CNG provides over CryptoAPI. To protect against this attack vector, Windows 8.1 introduced LSA protection, which sets up LSASS to run as Protected Process Lite (PPL):
[...] a Protected Process can be accessed by an unprotected process only with very limited privileges:PROCESS_QUERY_LIMITED_INFORMATION
,PROCESS_SET_LIMITED_INFORMATION
,PROCESS_TERMINATE
andPROCESS_SUSPEND_RESUME
. This set can even be reduced for some highly-sensitive processes.
LSA protection is off by default, but when enabled, it makes it significantly more difficult for a bad actor to compromise LSASS and extract private keys from CNG since they first have to find a way to disable PPL.
Credential Guard
LSA protection makes it more difficult to compromise LSASS, but it’s still possible. To further raise the bar, Windows 10 therefore introduced Credential Guard:
Protected processes are a kernel construct; malware running in the kernel can modify the structures necessary to mark a process as protected and, well, unprotect them. […] That’s not strictly true anymore with the introduction of Hypervisor-protected code integrity (HVCI), which is specifically designed to protect the kernel against tampering.
Credential Guard uses virtualization-based security (VBS) and runs parts of LSASS (LsaIso) at a different trust level, which is separated from the rest of the kernel by the hypervisor. When Credential Guard is enabled, even LSASS can’t access non-exportable private keys anymore.
Takeaway
Any software-based key store has its limitations, and if you’re really concerned about the safety of encryption keys, you might be better off with a hardware-based solution. But some software-based key stores clearly provide better protection than others. CNG goes through much greater lengths to protect encryption keys than CryptoAPI, and with LSA protection and Credential Guard, you can further strengthen these protections. Whenever we need to store encryption keys, we should therefore prefer CNG over CryptoAPI.