Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Perform cryptographic operations with asymmetric keys

Foreword

As the title suggests, in this pill we will focus on asymmetric keys; however, note that the next pill will be dedicated to the use of symmetric keys.

A TPM is capable of performing basic cryptographic operations such as encryption/decryption and signing/verification. To use these methods, keep in mind that the key must first be loaded (i.e., TPM2_Load).

Info

The code of the CLI that you will see below is fully available here.

Note: if you want to use a real TPM in the examples, you can add --use-real-tpm flag in go run github.com/loicsikidi/tpm-pills/examples/05-pill create command.

Encryption / Decryption

Let’s begin with encryption + decryption using an asymmetric key. This function ensures data integrity and confidentiality and follows this pattern:

  1. The public key encrypts a payload
  2. The private key decrypts the payload

However, asymmetric encryption has a limitation: maximum payload size. For example, here are the constraints when using an RSA key with OAEP padding1:

AlgorithmParametersMaximum message length
(in bytes)
RSA_DECRYPT_OAEP_2048_SHA256k = 256; hLen = 32;190
RSA_DECRYPT_OAEP_3072_SHA256k = 384; hLen = 32;318
RSA_DECRYPT_OAEP_4096_SHA256k = 512; hLen = 32;446
RSA_DECRYPT_OAEP_4096_SHA512k = 512; hLen = 64;382

Table: RSA OAEP maximum payload size

In short, asymmetric encryption is not recommended for variable-length messages, as some values may exceed the size limit. In such cases, hybrid cryptography2 is preferred.

Now that this is clarified, let’s look at a practical example! First, we’ll create a key with the following attributes:

signdecryptrestricted
010

To do so, run the command below:

# Note: the key will be stored in the current directory 
# with the names `tpmkey.pub`, `tpmkey.priv` and `public.pem`
go run github.com/loicsikidi/tpm-pills/examples/05-pill create --type decrypt
# output: Ordinary key created successfully 🚀
Info

Exceptionally this key is of type RSA because the command TPM2_ECC_Decrypt3 was introduced in a recent version of the spec (i.e., 1.83), and very few TPMs currently comply with it.

Typically, a TPM version can be updated via a BIOS firmware upgrade.

Now, let’s encrypt a message using the public key:

# Encrypt a blob using the public key
go run github.com/loicsikidi/tpm-pills/examples/05-pill encrypt --key ./public.pem \
--message 'Hello TPM Pills!' --output ./blob.enc
# output: Encrypted message saved to ./blob.enc 🚀

# Alternatively, you can use the `openssl` command to encrypt the blob
openssl pkeyutl -encrypt -in <(echo -n 'Hello TPM Pills!') -out ./blob.enc \
-pubin -inkey public.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256
Note

We are using OAEP padding with SHA256 hashing. It's an arbitrary choice.

Technically, a TPM is not required for encryption; however, in constrained environments (e.g., IoT or embedded systems), the TPM2_RSA_Encrypt command can be used.

All prerequisites are now met to decrypt:

# Decrypt the blob using the private key held in the TPM
go run github.com/loicsikidi/tpm-pills/examples/05-pill decrypt --public ./tpmkey.pub \
--private ./tpmkey.priv --in ./blob.enc
# output: Decrypted "Hello TPM Pills!" successfully 🚀

# clean up
go run github.com/loicsikidi/tpm-pills/examples/05-pill cleanup
rm ./tpmkey.pub ./tpmkey.priv ./public.pem ./blob.enc

Under the hood, the command looks like this:

decryptRsp, _ := tpm2.RSADecrypt{
	KeyHandle: tpm2.NamedHandle{ // reference to the key doing the job
		Handle: loadedOrdinaryKey.ObjectHandle,
		Name:   loadedOrdinaryKey.Name,
	},
	CipherText: tpm2.TPM2BPublicKeyRSA{Buffer: ciphertext}, // encrypted blob
	InScheme: tpm2.TPMTRSADecrypt{
		Scheme: tpm2.TPMAlgOAEP, // algorithm
		Details: tpm2.NewTPMUAsymScheme(
			tpm2.TPMAlgOAEP,
			&tpm2.TPMSEncSchemeOAEP{
				HashAlg: tpm2.TPMAlgSHA256, // hash algorithm
			},
		),
	},
}.Execute(tpm)
fmt.Println(decryptRsp.Message.Buffer) // original message in plain text

Signature

Now, let’s see how to perform a digital signature using a TPM.

This time the flow is reversed: the private key signs, and the public key verifies the integrity.

First, let’s create a signing key:

go run github.com/loicsikidi/tpm-pills/examples/05-pill create --type signer
# output: Ordinary key created successfully 🚀

This key has the following attributes:

signdecryptrestricted
100

Run the following commands to create and verify a message signature:

go run github.com/loicsikidi/tpm-pills/examples/05-pill sign --public ./tpmkey.pub \
--private ./tpmkey.priv --message 'Hello TPM Pills!' --output ./message.sig
# output: Signature saved to ./message.sig 🚀

go run github.com/loicsikidi/tpm-pills/examples/05-pill verify --key ./public.pem \
--signature ./message.sig --message 'Hello TPM Pills!'
# output: Signature verified successfully 🚀

# Alternatively, you can use the `openssl` command to verify the signature
openssl dgst -sha256 -verify ./public.pem -signature ./message.sig <(echo -n 'Hello TPM Pills!')
# output: Verified OK
Tip

As before, the TPM provides a command for signature verification (i.e., TPM2_VerifySignature4).

In code, the TPM_Sign command looks like this:

signRsp, _ := tpm2.Sign{
	KeyHandle: tpm2.NamedHandle{ // reference to the key doing the job
		Handle: loadedOrdinaryKey.ObjectHandle,
		Name:   loadedOrdinaryKey.Name,
	},
	Digest:     digest, // payload's digest 
}.Execute(tpm)

Deep dive: signing with a restricted key

In the previous pill, we introduced the concept of a restricted key. To recap, this property allows a key to sign or encrypt/decrypt TPM-internal objects. Now the question arises: is it possible to sign external data with a restricted key? The answer is yes, but under certain conditions.

A restricted signing key is mainly used to produce attestations in the TPMS_ATTEST5 format.

What is an attestation?

It's a proof generated by a TPM and intended for a third party. The attestation typically relates to some internal TPM state. This document is always signed by a restricted signing key, commonly known as an Attestation Key.

In a simplified view, a TPMS_ATTEST structure looks like this:

You’ll notice the structure includes a header (TPM_GENERATED_VALUE), which is critical — it indicates that the data within the attestation (i.e., the body) originates from a TPM. In our case, though, the data does not come from a TPM — but don’t worry, the spec takes this situation into account. The signature is accepted only if the TPM produced the digest itself using TPM2_Hash. Let’s break this down using the diagram below:

As shown, the TPM2_Hash6 command returns two elements: a digest and a ticket. The first is straightforward — but what is a ticket? Think of the TPM as a stateless component. Like an HTTP cookie, a ticket carries information between TPM commands. In our case, the ticket tells TPM_Sign that the TPM itself produced the digest.

How does the TPM trust the ticket?

The ticket is a signed document created by the TPM using an internal, non-exportable HMAC key. When a command supports tickets, the TPM first verifies the signature before proceeding.

Here's the equivalent in code:

// without error handling for more clarity
rspHash, _ := tpm2.Hash{ // hash performed by the TPM
	Data:      tpm2.TPM2BMaxBuffer{Buffer: []byte(message)},
	HashAlg:   tpm2.TPMAlgSHA256,
	Hierarchy: tpm2.TPMRHOwner,
}.Execute(tpm)

signRsp, _ := tpm2.Sign{
	KeyHandle: tpm2.NamedHandle{ // reference to the key doing the job
		Handle: loadedOrdinaryKey.ObjectHandle,
		Name:   loadedOrdinaryKey.Name,
	},
	Digest:     rspHash.OutHash, // digest
	Validation: rspHash.Validation, // ticket
}.Execute(tpm)

As a demonstration, here’s the same example as earlier, but this time using a restricted key:

signdecryptrestricted
101
go run github.com/loicsikidi/tpm-pills/examples/05-pill create --type restrictedSigner

# Sign a message using the private key held in the TPM
go run github.com/loicsikidi/tpm-pills/examples/05-pill sign --public ./tpmkey.pub --private ./tpmkey.priv --message 'Hello TPM Pills!' --output ./message.sig
# output: Signature saved to ./message.sig 🚀

# Verify the signature using the public key
go run github.com/loicsikidi/tpm-pills/examples/05-pill verify --key ./public.pem --signature ./message.sig --message 'Hello TPM Pills!'
# output: Signature verified successfully 🚀

# Alternatively, you can use the `openssl` command to verify the signature
openssl dgst -sha256 -verify ./public.pem -signature ./message.sig <(echo -n 'Hello TPM Pills!')
# output: Verified OK

# Clean up
go run github.com/loicsikidi/tpm-pills/examples/05-pill cleanup
rm ./tpmkey.pub ./tpmkey.priv ./public.pem ./message.sig

Conclusion

In this pill, we explored how to perform cryptographic operations with an asymmetric key pair (encryption/decryption, signing/verification). We haven’t covered every function (e.g., Elliptic Curve Diffie-Hellman, ECDH), but we’ve tackled the essentials. We also took a detour to introduce two key concepts: attestation and ticket.

Next Pill...

...we will focus on symmetric keys and how to use them.


🚧 TPM Pills is in beta 🚧

  • if you encounter problems 🙏 please report them on the tpm-pills issue tracker
  • if you think that TPM Pills should cover a specific topic which isn't in the roadmap, let's initiate a discussion

  1. see Optimal asymmetric encryption padding article on Wikipedia

  2. see hybrid cryptosystem article on Wikipedia

  3. https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-3-Version-184_pub.pdf#page=110

  4. https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-3-Version-184_pub.pdf#page=165

  5. https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-2-Version-184_pub.pdf#page=158

  6. security note: if the payload to be hashed includes the TPM_GENERATED_VALUE header, the command will not return a ticket.