Demystifying Trusted Platform Modules, Part 4
This is part 4 of a 6-part series on Trusted Platform Modules. If you missed the earlier parts, be sure to check those out first!
Summit Embedded Blog: TPM Series
Encrypt & Decrypt Data (this post)
Generate a Device Identity Certificate
Sign Data With A Certificate
This is the moment you’ve been waiting for. You’re over halfway through this blog series and just now using the TPM for its true purpose. It’s time to encrypt! Let’s run a few commands on the TPM we had previously set up.
Create A Private, Primary Key
First, we must create a primary key. Each hierarchy (see Part 2) has only one primary key. This is not the key we will use to encrypt or sign data. We can derive many unique “child” keys from this primary key, which will then be used to encrypt and sign data. There are a few reasons for this architecture, which are beyond the scope of the tutorial. The most important reason is that the primary key can be used to export encrypted backup files of the various child keys. Remember that the TPM has rather limited internal storage space, so exporting & later importing keys (in a secure, encrypted form) can optimize this constraint. Because the primary key never leaves the specific TPM you’re using, it’s still a true statement to say “The private data can never be extracted from the TPM.” even though you can extract the private date in encrypted form. Only this specific TPM can decrypt such an export file. No other TPM or device can decrypt it.
Let’s run this command to generate the primary key:
sudo tpm2_createprimary -C o -P "" -p oprim -c owner-primary.context;
Some notes about this command:
This creates a primary key in TPM RAM (not persistent, yet).
It's possible to use this key by referring to it with it's owner-primary.context file as it's handle.
If the TPM is power cycled in this state, the primary key is lost and must be regenerated.
-P
(uppercase P) is the parent password to access the hierarchy. Since this is the primary key, it's not needed.-p
(lowercase p) is the password we're creating for this key. In this case "oprim"What is this .ctx (context) file? Context files contain metadata about data stored in a TPM (attritubes, hierarchy, etc). It can be used to reference this key that is currently stored in RAM (and doesn’t yet have a handle).
Let’s save this primary key to persistent storage. This step is not strictly required. The primary key is generated from a seed that was randomly generated last time the TPM was cleared. So if you don’t clear your TPM, you can recreate an identical primary key on a future power cycle by re-running the tpm2_createprimary
command again. But let’s save it to handle 0x81000001 just for clarity in this example:
sudo tpm2_evictcontrol -C o -c owner-primary.context 0x81000001;
You can optionally verify that the key was written by running the command:
sudo tpm2_readpublic --object-context 0x81000001;
I also like to run rm owner-primary.context;
at this time. This is optional, but your work directory gets messy without this step. If needed, you can regenerate the context later with tpm2_contextsave
but it shouldn't be needed.
Create a Private, Child Key
Next, we will create our key pair that will encrypt & decrypt our sample string. Run the following commands:
sudo tpm2_create --parent-context 0x81000001 -P oprim -p oEncPass -c owner-identity.context;
sudo tpm2_evictcontrol -C o -c owner-identity.context 0x81000002;
sudo rm owner-identity.context;
Similar to making the primary key, we create a new private key in the TPM’s RAM. In this case:
We derive it from the primary key with
--parent-context 0x81000001.
We could also have used theowner-primary.context
file instead.We must provide the primary key’s password:
oprim
We set a password for our child key to
oEncPass
Again we use the owner hierarchy and create a new context file called
owner-identity.context
Then we store that private key to persistent storage with the tpm2_evictcontrol
command. We selected handle 0x81000002 to store it. We’ll use this handle frequently in the future (any time we want to reference the key during normal operation).
It is important to clearly document the handles that you select. For example, you might document something like the following for your application developers:
“Handle 0x81000002 contains an asymmetric key pair used to sign the device’s unique identity certificate, and handle 0x81000003 contains a symmetric key used to encrypt & decrypt our application’s sensitive data”.
Your developers will use these handles when writing application software to work with the TPM.
Encrypt Our Message
Now that our encryption key is set up, we can encrypt a message with just a single command:
echo "Summit was here, in encrypted form" | sudo tpm2_rsaencrypt -c 0x81000002 -o message.enc;
This uses keypair 0x8100002 to encrypt the message that we echoed. The ciphertext is output into the file message.enc. View the ciphertext with sudo cat message.enc;
The key generation commands use RSA encryption (asymmetric) by default. Thus, we use tpm2_rsaencrypt
and tpm2_rsadecrypt
in this example. If we used a symmetric key type (like AES), we could test with tpm2_encryptdecrypt
for both encrypting and decrypting. Use the “-G <algo>
” argument when generating keys to select a different algorithm.
Decrypt Our Message
Likewise, we can decrypt our ciphertext with just onecommand:
sudo tpm2_rsadecrypt -c 0x81000002 -p oEncPass message.enc
This uses keypair 0x8100002 to decrypt the message that we previously encrypted.
Note that -p oEncPass
password is needed only to decrypt. If you omit this, you’ll get a not-so-descriptive “HMAC authorization check failed” error.
That’s it for encrypting & decrypting. But encryption with unique, device-specific keys is not very useful on it’s own. The real power comes when you wrap an x509 certificate around the device key, and sign it with your organization’s key. See Part 5 of our TPM series for an example of that: Part 5 - Generate a Device Identity Certificate