In a previous post, I talked a bit about what a Trusted Platform Module (TPM) is and what you can do with one. Now we'll look into actually working with a TPM by encrypting some data.
n.b.: this will focus on Windows, since that's what I'm most familiar with.
CryptoAPI Next Generation
If you've written crypto code for Windows in the past, you probably using the CryptoAPI (CAPI). In Windows Vista, Microsoft introduced a replacement, the creatively-named CryptoAPI Next Generation (CNG), with more algorithms supported and a very different API. It also provides by far the easiest interface for working with a TPM!
Unfortunately, how to do so isn't particularly well-documented. We'll get to that in a moment.
The functions in CNG are mostly divided into the
NCrypt families. Broadly speaking,
BCrypt functions are cryptographic primitives, while
NCrypt functions are for key storage and retrieval; a less-accurate generalization is that
BCrypt functions are mostly for hashing and symmetric crypto, while
NCrypt ones are mostly for asymmetric (including encryption, decryption, secret-sharing, signing, etc.).
CNG + TPM: The Short Version
The tl;dr of encrypting with a TPM via CNG is that you open a key storage provider of type
MS_PLATFORM_KEY_STORAGE_PROVIDER, get a handle to a key in that storage provider (which is, in fact, the TPM), and work with that key handle as usual:
NCRYPT_KEY_HANDLE hRsaKey = NULL; NCRYPT_PROV_HANDLE hStorageProv = NULL; // Open the TPM-based key storage provider. NCryptOpenStorageProvider(&hStorageProv, MS_PLATFORM_KEY_STORAGE_PROVIDER, 0); // Attempt to open the key with our key identifier. NCryptOpenKey(hStorageProv, &hRsaKey, SOME_KEY_IDENTIFIER, 0, 0); // Encrypt some data with the key we just opened. NCryptEncrypt(hRsaKey, pDataIn, cbDataIn, &paddingInfo, &pDataOut, dwBufferSize, &cbDataOut, NCRYPT_PAD_OAEP_FLAG);
CNG + TPM: the Long Version
Of course, in real code, you'll want to handle errors and so on. You'll also notice that, while encryption is quick, decryption is not -- on my machine decryption throughput is on the order of a few Kb/s. (Encryption is quite fast because it only uses the public part of the TPM's RSA key, which, as it's public, can be released from the TPM so encryption can be done with your computer's CPU. Decryption requires the private component of the key, so it must be performed on the much-less-powerful TPM itself.)
To address this, we can do a fairly standard thing:
- Generate a random AES key
- Encrypt the data with the AES key
- Encrypt the AES key with a TPM-stored RSA key.
- Store the RSA-encrypted AES key with the AES-encrypted data.
The code to do this gets quite large, so I've pulled it into a separate Github project for you to check out.
If you don't want to use CNG, there are a few other libraries to check out.
First is this project from Microsoft Research. It provides a reasonably nice interface to a TPM for either C++ or C#. Its API is more of a TPM interface than a crypto interface, though, so it requires more in-depth knowledge of how exactly a TPM works.
If you are particularly masochistic, you can use the TPM Base Services API. The primary function in this API is
Tbsip_Submit_Command, which takes a byte array that is fed directly to the TPM microchip.
Finally, for Linux we have TrouSerS, an open-source implementation of the Trusted Computing Software Stack. It only supports version 1.2 of the TPM spec, however; version 2.0 is sufficiently different that the project owners decided not to support it.
This StackOverflow question is a wonderfully sourced rundown of the author's attempts to find a way to encrypt bytes using a TPM. It's well-worth reading over if you're interested in other approaches or a more in-depth look.
This presentation is an excellent overview of the Platform Crypto Provider. It also includes a fact I haven't found documented anywhere else: if you create a
REG_SZ registry value named
ProviderTraces under the key
HKLM\SYSTEM\CurrentControlSet\Services\TPM with the path to a folder, Windows will print out debugging information for the TPM commands, including the actual byte arrays sent to the chip.