Skip to main content

Raymii.org Raymii.org Logo

אֶשָּׂא עֵינַי אֶל־הֶהָרִים מֵאַיִן יָבֹא עֶזְרִֽי׃
Home | About | All pages | Cluster Status | RSS Feed

Put your SSH keys in your TPM chip!

Published: 10-04-2026 19:37 | Author: Remy van Elst | Text only version of this article



I've got a long history with hardware security modules, both professionally and for fun. For the longest time, my SSH private key has lived inside a hardware token of some sort, be it the Nitrokey, the Smartcard-HSM or a Yubikey. The private key never leaves the device, you yourself can't even extract it, neither can malware. It does not live on your filesystem or in an ssh-agent (in memory) and some hardware keys even require a physical touch to use the key. Way more secure than the file ~/.ssh/id_rsa. I do hope you have a password on your private keys. Most, if not all modern machines come with a comparable hardware solution, a TPM (trusted platform module). It's required for Windows 11 so most hardware has one. Its often used to verify the boot process. You can however, also use it to store your SSH keys and in this guide I'll show you how I recently did just that.

nuvoton tpm chip

A Nuvoton NPCT750 TPM

This guide is based on the official documentation

Do note that this does not work on WSL.

TPM vs HSM

By HSM I mean all of those sorts of modules I mentioned earlier, the Yubikey, Nitrokey, Smartcard-HSM, etc. Or a big Gemalto one if you're that kind of guy. As long as you can use it with a PKCS#11 module.

The TPM module is a bit less secure for this purpose since it is device bound. Meaning that it does not require physical presence. You have to plug in a Yubikey to use it, but the TPM is always there.

Using a TPM is more secure than using files on your filesystem, but a bit less secure than an actual (portable) HSM you can plug in.

A big warning is that a lot desktop motherboards (at least consumer oriented ones) wipe the TPM when you update the BIOS. I have chosen to generate an SSH key (on a secure, offline machine) and put that in the TPM, instead of letting the TPM generate one. That key can be securely backed up. And it might even help in the case of vulnerabilities like ROCA (CVE-2017-15361), where millions of Infineon TPM chips produced weak RSA keys.

TPM Setup for SSH

First install the required software:

apt install tpm2-tools libtpm2-pkcs11-tools libtpm2-pkcs11-1 opensc tpm2-abrmd

Or if you're on Arch Linux:

pacman -Syu tpm2-tools tpm2-tss tpm2-pkcs11 tpm2-abrmd

I recompiled tpm2-tools with --with-fapi=no because I got errors on every tpm related command:

WARNING:fapi:src/tss2-fapi/api/Fapi_List.c:228:Fapi_List_Finish() Profile of path not provisioned: /HS/SRK  

I tried to use the tpm2_provision command beforehand but that did not work.

Add yourself to the tss group:

 sudo usermod -a -G tss "$USER"

Logout and log back in to make this change active.

Creating a token

Create a persistent PKCS#11 store, a directory that contains the actual keys and other metadata needed by the PKCS#11 interface.

The private key is not stored directly in the TPM chip, instead, it's encrypted and saved in a SQLite file in that directory. A TPM only has a limited amount of storage memory but can use many keys because the private keys are not stored in the TPM. The key is loaded into the TPM when it is used.

mkdir ~/.tpm2_pkcs11
tpm2_ptool init

Output:

action: Created
id: 1

Add a token for your ssh key. You can repeat this step if you have multiple SSH keys. pid is the Primary Token ID, which the previous command's output gave you.

The user pin and Security Officer pin must be given as parameters to this command. We don't want that in our shell history, so put the passwords (without newlines) inside two text files, sopin.txt and userpin.txt, then use shell process substitution to echo the contents of those files.

Make sure that there are no newlines. Check with object dump:

od -c userpin.txt 

Output:

0000000   U   S   E   R   P   I   N  \n
0000010

If the output has a \n, remove that last byte:

[ "$(tail -c1 userpin.txt)" = "" ] && truncate -s -1 userpin.txt

Verify:

od -c userpin.txt 

Output:

0000000   U   S   E   R   P   I   N
0000007

Add the token:

tpm2_ptool addtoken --pid 1 --label sshtoken --sopin $(cat sopin.txt) --userpin $(cat userpin.txt)

The User pin is the password (it doesn't have to be a pin code, can be a long string) is the password you put in every time you want to use the key. The SO pin (Security Officer) is for key management (and a backup pin if you need to reset the user pin). You can verify the newly added object:

$ tpm2_ptool verify --label sshtoken --userpin $(cat userpin.txt)
config:
  pss-sigs-good: true
  token-init: true
label: sshtoken
objects: []
pin:
  user:
    seal-auth: 1...6
wrappingkey:
  auth: 1...6
  hex: '3...6'

Importing an SSH key into the token

You can let the TPM generate an SSH key, but there are stories online of TPM data being gone after a BIOS update. You can also export TPM keys, but in my experience that doesn't always work inside a different device. Therefore I generate new SSH keys on an offline system, back them up and import them into the TPM. RSA and ecc256 work as algorithms, others might not.

Create a working directory and copy the existing SSH key to there:

mkdir sshtpm
cd sshtpm
cp path/to/id_rsa ./tpm_key

To import a key into the TPM, you must remove any password on it and convert it to PEM format:

ssh-keygen -f tpm_key -mPEM -ep

Finally import the key into the token:

tpm2_ptool import --label sshtoken --key-label sshkey1 --userpin $(cat userpin.txt) --privkey tpm_key --algorithm rsa

Remove the private key file afterwards:

rm tpm_key

Use the SSH key in the TPM

You must set an environment variable to make the library path known to the tooling:

export TPM2_PKCS11_SO=/usr/lib/pkcs11/libtpm2_pkcs11.so

You can put that in your .bashrc file. The path can vary as well, so make sure to check other folders under /usr/lib, for example on Ubuntu.

Add the same path to the top of your ~/.ssh/config file:

PKCS11Provider /usr/lib/pkcs11/libtpm2_pkcs11.so

Use the following command to get a list of public keys in the TPM:

ssh-keygen -D $TPM2_PKCS11_SO

Output:

ssh-rsa AA...C1 sshkey1
ecdsa-sha2-nistp256 A...Fc= sshkey2 

You can now SSH into your servers using this key, but you will be queried for the User Pin:

ssh root@server
**Enter PIN for 'sshkey1':**
Last login: Thu Apr  9 14:32:50 2026 from
192.0.2.10 root@server:~# 

If you want to add the keys to the SSH agent and be asked only once for the pin, you can use this command:

ssh-add -s $TPM2_PKCS11_SO

You can get some more info on the TPM module and the key material with the following commands. Info about objects via the PKCS#11 provider:

pkcs11-tool --module /usr/lib/pkcs11/libtpm2_pkcs11.so --list-objects

Output:

Using slot 0 with a present token (0x1)
Public Key Object; RSA 4096 bits
  label:      sshkey1
  ID:         ...
  Usage:      encrypt, verify
  Access:     none
  uri:        pkcs11:model=NPCT75x;manufacturer=Nuvoton;serial=0...1;token=sshtoken;id=%...;object=sshkey1;type=public

Info via the tpm2 tooling:

tpm2_ptool listobjects --label sshtoken

Output:

- CKA_CLASS: CKO_PRIVATE_KEY
  CKA_ID:
  - '3.7'
  CKA_KEY_TYPE: CKK_RSA
  CKA_LABEL: sshkey1
  id: 1
- CKA_CLASS: CKO_PUBLIC_KEY
  CKA_ID:
  - '3.7'
  CKA_KEY_TYPE: CKK_RSA
  CKA_LABEL: sshkey1
  id: 2

The ssh keys inside the token:

tpm2_ptool listtokens --pid 1

Output:

- id: 1
  label: sshtoken
- id: 2
  label: sshtoken2
Tags: cryptoki , hsm , linux , mod_nss , openssl , pkcs11 , smartcard , smartcard-hsm , tpm , tutorials