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