How can I encrypt bytes using a machine's TPM module?
CryptProtectData
Windows provides a (relatively) simple API to encrypt a blob using the CryptProtectData
API, which we can wrap an easy to use function:
public Byte[] ProtectBytes(Byte[] plaintext)
{
//...
}
The details of ProtectBytes
are less important than the idea that you can use it quite easily:
- here are the bytes I want encrypted by a secret key held in the
System
- give me back the encrypted blob
The returned blob is an undocumented documentation structure that contains everything needed to decrypt and return the original data (hash algorithm, cipher algorithm, salt, HMAC signature, etc).
For completeness, here's the sample pseudocode implementation of ProtectBytes
that uses the Crypt API
to protect bytes:
public Byte[] ProtectBytes(Byte[] plaintext)
{
//Setup our n-byte plaintext blob
DATA_BLOB dataIn;
dataIn.cbData = plaintext.Length;
dataIn.pbData = Addr(plaintext[0]);
DATA_BLOB dataOut;
//dataOut = EncryptedFormOf(dataIn)
BOOL bRes = CryptProtectData(
dataIn,
null, //data description (optional PWideChar)
null, //optional entropy (PDATA_BLOB)
null, //reserved
null, //prompt struct
CRYPTPROTECT_UI_FORBIDDEN || CRYPTPROTECT_LOCAL_MACHINE,
ref dataOut);
if (!bRes) then
{
DWORD le = GetLastError();
throw new Win32Error(le, "Error calling CryptProtectData");
}
//Copy ciphertext from dataOut blob into an actual array
bytes[] result;
SetLength(result, dataOut.cbData);
CopyMemory(dataOut.pbData, Addr(result[0]), dataOut.cbData);
//When you have finished using the DATA_BLOB structure, free its pbData member by calling the LocalFree function
LocalFree(HANDLE(dataOut.pbData)); //LocalFree takes a handle, not a pointer. But that's what the SDK says.
}
How to do the same with the TPM?
The above code is useful for encrypting data for the local machine only. The data is encrypted using the System
account as the key generator (details, while interesting, are unimportant). The end result is that I can encrypt data (e.g. a hard drive encryption master key) that can only be decrypted by the local machine.
Now it's time to take this one step further. I want to encrypt some data (e.g. a hard drive encryption master key) that can only be decrypted by the local TPM. In other words, I want to replace the Qualcomm Trusted Execution Environment (TEE) in the block diagram below for Android, with the TPM in Windows:
Note: I realize that the TPM doesn't do data-signing (or if it does, it does not guarantee that signing the same data will give the same binary output every time). Which is why I'd be willing to replace "RSA signing" with "encrypting a 256-bit blob with a hardware bound key".
So where's the code?
The problem is that TPM programming is completely undocumented on MSDN. There is no API available to perform any operations. Instead you have to find yourself a copy of the Trusted Computing Group's Software Stack (aka TSS), figure out what commands to send to the TPM, with payloads, in what order, and call Window's Tbsip_Submit_Command function to submit commands directly:
TBS_RESULT Tbsip_Submit_Command(
_In_ TBS_HCONTEXT hContext,
_In_ TBS_COMMAND_LOCALITY Locality,
_In_ TBS_COMMAND_PRIORITY Priority,
_In_ const PCBYTE *pabCommand,
_In_ UINT32 cbCommand,
_Out_ PBYTE *pabResult,
_Inout_ UINT32 *pcbOutput
);
Windows has no higher level API to perform actions.
It's the moral equivalent of trying to create a text file by issuing SATA I/O commands to your hard drive.
Why not just use Trousers
The Trusted Computing Group (TCG) did define their own API: TCB Software Stack (TSS). An implementation of this API was created by some people, and is called TrouSerS. A guy then ported that project to Windows.
The problem with that code is that it is not portable into the Windows world. For example, you can't use it from Delphi, you cannot use it from C#. It requires:
- OpenSSL
- pThread
I just want the code to encrypt something with my TPM.
The above CryptProtectData
requires nothing other than what's in the function body.
What is the equivalent code to encrypt data using the TPM? As others have noted, you probably have to consult the three TPM manuals, and construct the blobs yourself. It probably involves the TPM_seal
command. Although I think I don't want to seal data, I think I want to bind it:
Binding – encrypts data using TPM bind key, a unique RSA key descended from a storage key. Sealing – encrypts data in a similar manner to binding, but in addition specifies a state in which TPM must be in order for the data to be decrypted (unsealed)
I try to read the three required volumes in order to find the 20 lines of code I need:
- Part 1 - Design Principles
- Part 2 - Structures of the TPM
- Part 3 - Commands
But I have no idea what I'm reading. If there was any kind of tutorial or examples, I might have a shot. But I'm completely lost.
So we ask Stackoverflow
In the same way I was able to provide:
Byte[] ProtectBytes_Crypt(Byte[] plaintext)
{
//...
CryptProtectData(...);
//...
}
can someone provide the corresponding equivalent:
Byte[] ProtectBytes_TPM(Byte[] plaintext)
{
//...
Tbsip_Submit_Command(...);
Tbsip_Submit_Command(...);
Tbsip_Submit_Command(...);
//...snip...
Tbsip_Submit_Command(...);
//...
}
that does the same thing, except rather than a key locked away in System
LSA, is locked away in the TPM?
Start of Research
I don't know exactly what bind means. But looking at TPM Main - Part 3 Commands - Specification Version 1.2, there is a mention of bind:
10.3 TPM_UnBind
TPM_UnBind takes the data blob that is the result of a Tspi_Data_Bind command and decrypts it for export to the User. The caller must authorize the use of the key that will decrypt the incoming blob. TPM_UnBind operates on a block-by-block basis, and has no notion of any relation between one block and another.
What's confusing is there is no Tspi_Data_Bind
command.
Research Effort
It is horrifying how nobody has ever bothered to document the TPM or its operation. It's as if they spent all their time coming up with this cool thing to play with, but didn't want to deal with the painful step of making it usable for something.
Starting with the (now) free book A Practical Guide to TPM 2.0: Using the Trusted Platform Module in the New Age of Security:
Chapter 3 - Quick Tutorial on TPM 2.0
The TPM has access to a self-generated private key, so it can encrypt keys with a public key and then store the resulting blob on the hard disk. This way, the TPM can keep a virtually unlimited number of keys available for use but not waste valuable internal storage. Keys stored on the hard disk can be erased, but they can also be backed up, which seemed to the designers like an acceptable trade-off.
How can I encrypt a key with the TPM's public key?
Chapter 4 - Existing Applications That Use TPMs
Applications That Should Use the TPM but Don’t
In the past few years, the number of web-based applications has increased. Among them are web-based backup and storage. A large number of companies now offer such services, but as far as we are aware, none of the clients for these services let the user lock the key for the backup service to a TPM. If this were done, it would certainly be nice if the TPM key itself were backed up by duplicating it on multiple machines. This appears to be an opportunity for developers.
How does a developer lock a key to the TPM?
Chapter 9 - Heirarchies
USE CASE: STORING LOGIN PASSWORDS
A typical password file stores salted hashes of passwords. Verification consists of salting and hashing a supplied password and comparing it to the stored value. Because the calculation doesn’t include a secret, it’s subject to an offline attack on the password file.
This use case uses a TPM-generated HMAC key. The password file stores an HMAC of the salted password. Verification consists of salting and HMACing the supplied password and comparing it to the stored value. Because an offline attacker doesn’t have the HMAC key, the attacker can’t mount an attack by performing the calculation.
This could work. If the TPM has a secret HMAC key, and only my TPM knows the HMAC key, then I could replace "Sign (aka TPM encrypt with it's private key)" with "HMAC". But then in the very next line he reverses himself completely:
TPM2_Create, specifying an HMAC key
It's not a TPM secret if I have to specify the HMAC key. The fact that the HMAC key isn't secret makes sense when you realize this is the chapter about cryptographic utilities that the TPM provides. Rather than you having to write SHA2, AES, HMAC, or RSA yourself, you can re-use what the TPM already has laying around.
Chapter 10 - Keys
As a security device, the ability of an application to use keys while keeping them safe in a hardware device is the TPM’s greatest strength. The TPM can both generate and import externally generated keys. It supports both asymmetric and symmetric keys.
Excellent! How do you do it!?
Key Generator
Arguably, the TPM’s greatest strength is its ability to generate a cryptographic key and protect its secret within a hardware boundary. The key generator is based on the TPM’s own random number generator and doesn’t rely on external sources of randomness. It thus eliminates weaknesses based on weak softwaresoftware with an insufficient source of entropy.
Does the TPM have the ability to generate cryptographic keys and protect its secrets within a hardware boundary? Is so, how?
Chapter 12 - Platform Configuration Registers
PCRs for Authorization
USE CASE: SEALING A HARD DISK ENCRYPTION KEY TO PLATFORM STATE
Full-disk encryption applications are far more secure if a TPM protects theencryption key than if it’s stored on the same disk, protected only by a password. First, the TPM hardware has anti-hammering protection (see Chapter 8 for a detailed description of TPM dictionary attack protection), making a brute-force attack on the password impractical. A key protected only by software is far more vulnerable to a weak password. Second, a software key stored on disk is far easier to steal. Take the disk (or a backup of the disk), and you get the key. When a TPM holds the key, the entire platform, or at least the disk and the motherboard, must be stolen.
Sealing permits the key to be protected not only by a password but by a policy. A typical policy locks the key to PCR values (the software state) current at the time of sealing. This assumes that the state at first boot isn’t compromised. Any preinstalled malware present at first boot would be measured into the PCRs, and thus the key would be sealed to a compromised software state. A less trusting enterprise might have a standard disk image and seal to PCRs representing that image. These PCR values would be precalculated on a presumably more trusted platform. An even more sophisticated enterprise would use TPM2_PolicyAuthorize, and provide several tickets authorizing a set of trusted PCR values. See Chapter 14 for a detailed description of policy authorize and its application to solve the PCRbrittleness problem.
Although a password could also protect the key, there is a security gain even without a TPM key password. An attacker could boot the platform without supplying a TPMkey password but could not log in without the OS username and password. The OSsecurity protects the data. The attacker could boot an alternative OS, say from a live DVD or USB stick rather that from the hard drive, to bypass the OS login security. However, this different boot configuration and software would change the PCRvalues. Because these new PCRs would not match the sealed values, the TPM would not release the decryption key, and the hard drive could not be decrypted.
Excellent! This is exactly the use case I happen to want. It's also the use case the Microsoft uses the TPM for. How do I do it!?
So I read that entire book, and it provided nothing useful. Which is quite impressive because it's 375 pages. You wonder what the book contained - and looking back on it, I have no idea.
So we give up on the definitive guide to programming the TPM, and turn instead to some documentation from Microsoft:
From the Microsoft TPM Platform Crypto-Provider Toolkit. It mentions exactly what I want to do:
The Endorsement Key or EK
The EK is designed to provide a reliable cryptographic identifier for the platform. An enterprise might maintain a database of the Endorsement Keys belonging to the TPMs of all of the PCs in their enterprise, or a data center fabric controller might have a database of the TPMs in all of the blades. On Windows you can use the NCrypt provider described in the section “Platform Crypto Provider in Windows 8” to read the public part of the EK.
Somewhere inside the TPM is an RSA private key. That key is locked away in there - never to be seen by the outside world. I want the TPM to sign something with it's private key (i.e. encrypt it with it's private key).
So I want the most basic operation that can possibly exist:
Encrypt something with your private key. I'm not even (yet) asking for the more complicated stuff:
- "sealing" it based on PCR state
- creating a key and storing it in volatile or non-volatile memroy
- creating a symmetric key and trying to load it into the TPM
I am asking for the most basic operation a TPM can do. Why is it impossible to get any information about how to do it?
I can get random data
I suppose I was being glib when I said RSA signing was the most basic thing the TPM can do. The most basic thing the TPM can be asked to do is give me random bytes. That I have figured out how to do:
public Byte[] GetRandomBytesTPM(int desiredBytes)
{
//The maximum random number size is limited to 4,096 bytes per call
Byte[] result = new Byte[desiredBytes];
BCRYPT_ALG_HANDLE hAlgorithm;
BCryptOpenAlgorithmProvider(
out hAlgorithm,
BCRYPT_RNG_ALGORITHM, //AlgorithmID: "RNG"
MS_PLATFORM_CRYPTO_PROVIDER, //Implementation: "Microsoft Platform Crypto Provider" i.e. the TPM
0 //Flags
);
try
{
BCryptGenRandom(hAlgorithm, @result[0], desiredBytes, 0);
}
finally
{
BCryptCloseAlgorithmProvider(hAlgorithm);
}
return result;
}
The Fancy Thing
I realize the volume of people using the TPM is very low. That is why nobody on Stackoverflow has an answer. So I can't really get too greedy in getting a solution to my common problem. But the thing I'd really want to do is to "seal" some data:
- present the TPM some data (e.g. 32 bytes of key material)
- have the TPM encrypt the data, returning some opaque blob structure
- later ask the TPM to decrypt the blob
- the decryption will only work if the TPM's PCR registers are the same as they were during encryption.
In other words:
Byte[] ProtectBytes_TPM(Byte[] plaintext, Boolean sealToPcr)
{
//...
}
Byte[] UnprotectBytes_TPM(Byte[] protectedBlob)
{
//...
}
Cryptography Next Gen (Cng, aka BCrypt) supports TPM
The original Cryptography API in Windows was knows as the Crypto API.
Starting with Windows Vista, the Crypto API has been replaced with Cryptography API: Next Generation (internally known as BestCrypt, abbreviated as BCrypt, not to be confused with the password hashing algorithm).
Windows ships with two BCrypt providers:
- Microsoft Primitive Provider (
MS_PRIMITIVE_PROVIDER
) default: Default software implementation of all the primitives (hashing, symmetric encryption, digital signatures, etc) - Microsoft Platform Crypto Provider (
MS_PLATFORM_CRYPTO_PROVIDER
): Provider that provides TPM access
The Platform Crypto provider is not documented on MSDN, but does have documentation from a 2012 Microsoft Research site:
TPM Platform Crypto-Provider Toolkit
The TPM Platform Crypto Provider and Toolkit contains sample code, utilities and documentation for using TPM-related functionality in Windows 8. Subsystems described include the TPM-backed Crypto-Next-Gen (CNG) platform crypto-provider, and how attestation-service providers can use the new Windows features. Both TPM1.2 and TPM2.0-based systems are supported.
It seems that Microsoft's intent is to surface TPM crypto functionality with the Microsoft Platform Crypto Provider of the Cryptography NG API.
Public key encryption using Microsoft BCrypt
Given that:
- i want to perform RSA asymmetric encryption (using the TPM)
- Microsoft BestCrypt supports RSA asymmetric encryption
- Microsoft BestCrypt has a TPM Provider
a way forward might be to figure out how to do digital signing using the Microsoft Cryptography Next Gen API.
My next step will be to come up with the code to do encryption in BCrypt, with an RSA public key, using the standard provider (MS_PRIMITIVE_PROVIDER
). E.g.:
modulus
: 0xDC 67 FA F4 9E F2 72 1D 45 2C B4 80 79 06 A0 94 27 50 8209 DD 67 CE 57 B8 6C 4A 4F 40 9F D2 D1 69 FB 995D 85 0C 07 A1 F9 47 1B 56 16 6E F6 7F B9 CF 2A 58 36 37 99 29 AA 4F A8 12 E8 4F C7 82 2B 9D 72 2A 9C DE 6F C2 EE 12 6D CF F0 F2 B8 C4 DD 7C 5C 1A C8 17 51 A9 AC DF 08 22 04 9D 2B D7 F9 4B 09 DE 9A EB 5C 51 1A D8 F8 F9 56 9E F8 FB 37 9B 3F D3 74 65 24 0D FF 34 75 57 A4 F5 BF 55publicExponent
: 65537
With that code functioning, i may be able to switch to using the TPM Provider (MS_PLATFORM_CRYPTO_PROVIDER
).
2/22/2016: And with Apple being compelled to help decrypt user data, there is renewed interest in how to make the TPM perform the most simplest task that it was invented for - encrypting something.
It's roughly equivalent to everyone owning a car, but nobody knows how to start one. It can do really useful and cool things, if only we could get past Step 1.
Bonus Reading
- Android - Encryption - Storing the encrypted key
- Android Explorations - Revisiting Android disk encryption
- DPAPI Secrets. Security analysis and data recovery in DPAPI (Part 1)