Raymii.org
אֶשָּׂא עֵינַי אֶל־הֶהָרִים מֵאַיִן יָבֹא עֶזְרִֽי׃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
Table of Contents
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.

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