介紹如何在 Linux 系統中使用 TPM 2.0 產生真實的隨機亂數(true random numbers)。
TPM(Trusted Platform Module)是一個安全加密處理器,透過加密金鑰來保護硬體的安全,而 TPM 也同時意指安全加密處理器的國際標準(也稱為 ISO/IEC 11889),在實務上 TPM 可用於安全開機(secure boot)、金鑰儲存以及隨機亂數的產生等。
TPM 中含有硬體亂數生成器,在 TPM 2.0 的規範中的 11.4.11.2 Entropy Source and Collector 部份有提到 TPM 2.0 的硬體模組必須內建至少一個熵源(source of entropy,也就是亂數來源的意思):
A TPM should have at least one internal source of entropy, and possibly more. These sources could include noise, clock variations, air movement, and other types of events.
所以只要是合規的 TPM 處理器都可以用來生成真正的隨機亂數。
Linux 核心的 CONFIG_HW_RANDOM_TPM 選項可以啟用 TPM 硬體亂數生成器功能,這項功能可以將 TPM 做為 hwrng 設備,在 Linux 系統開機時讓核心從 TPM 取得亂數,並將亂數注入 /dev/hwrng
。
TPM 主要有 TPM 2.0 與 TPM 1.2 兩種版本,兩種版本有很大的差異,這裡我們只討論 TPM 2.0 的使用方式。若在 Linux 系統中要檢查自己的硬體是否有支援 TPM 2.0,可以查看 dmesg
的輸出,檢查是否有 TPM 的字樣:
# 查看 dmesg 輸出是否包含 tpm 字樣 dmesg | grep -i tpm
[ 0.000000] efi: ACPI 2.0=0x797c5000 ACPI=0x797c5000 TPMFinalLog=0x797e9000 SMBIOS=0x799fc000 SMBIOS 3.0=0x799fb000 ESRT=0x74185898 MEMATTR=0x7209b018 [ 0.011479] ACPI: TPM2 0x00000000797DB970 000034 (v03 GBT GBTUACPI 00000001 AMI 00000000) [ 0.011579] ACPI: Reserving TPM2 table memory at [mem 0x797db970-0x797db9a3] [ 5.875662] systemd[1]: systemd 252.6-1 running in system mode (+PAM +AUDIT +SELINUX +APPARMOR +IMA +SMACK +SECCOMP +GCRYPT -GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 -PWQUALITY +P11KIT +QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD -BPF_FRAMEWORK -XKBCOMMON +UTMP +SYSVINIT default-hierarchy=unified)
或是檢查系統上的 TPM 相關設備檔是否存在:
# 檢查 TPM 相關設備檔是否存在 ls /dev/tpm*
/dev/tpm0 /dev/tpmrm0
如果 /dev/tpm0
與 /dev/tpmrm0
兩個設備檔都存在,則代表系統有支援 TPM 2.0,如果只有 /dev/tpm0
存在,則代表系統只有支援 TPM 1.2。
如果 Linux 核心版本在 5.6 以上,也可以直接查看 TPM 的版本:
# 查看 TPM 的版本 cat /sys/class/tpm/tpm0/tpm_version_major
2
或是查看 TPM 設備資訊:
# 查看 TPM 設備版本 cat /sys/class/tpm/tpm0/device/description
TPM 2.0 Device
tpm2-tools 是以 tpm2-tss 為基礎所開發的 TPM 工具組,裡面包含許多加密與簽章相關的 TPM 工具程式,使用前可用 apt
安裝:
# 安裝 tpm2-tools
sudo apt install tpm2-tools
在大部份的 Linux 系統中,只有 root
管理者可以直接存取 TPM 的設備檔,若要讓一般使用者也可以使用 TPM 設備,必須將使用者加入對應的群組,以 Ubuntu 或 Debian 這類的 Linux 系統來說,對應的群組就是 tss
:
# 將 myuser 使用者加入 tss 群組 sudo usermod -a -G tss myuser
在 tpm2-tools 套件中,有一個 tpm2_getrandom
指令工具可以用來從 TPM 產生真正的隨機亂數:
# 使用 TPM 產生 8 位元組的隨機資料,以 16 進位輸出 tpm2_getrandom --hex 8
2c82b9ecc8934be0
若要以原始的亂數資料輸出,可以使用 -o
參數指定輸出的檔案:
# 使用 TPM 產生 8 位元組的隨機資料,輸出至檔案 tpm2_getrandom -o random.out 8
如果在執行 tpm2_getrandom
的時候,出現類似以下這樣的 Permission denied
錯誤,代表目前的使用者沒有權限存取 /dev/tpmrm0
設備檔:
ERROR:tcti:src/tss2-tcti/tcti-device.c:452:Tss2_Tcti_Device_Init() Failed to open specified TCTI device file /dev/tpmrm0: Permission denied ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-device.so.0 ERROR:tcti:src/tss2-tcti/tcti-device.c:452:Tss2_Tcti_Device_Init() Failed to open specified TCTI device file /dev/tpm0: Permission denied ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-device.so.0 WARNING:tcti:src/util/io.c:262:socket_connect() Failed to connect to host 127.0.0.1, port 2321: errno 111: Connection refused ERROR:tcti:src/tss2-tcti/tcti-swtpm.c:614:Tss2_Tcti_Swtpm_Init() Cannot connect to swtpm TPM socket ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-swtpm.so.0 WARNING:tcti:src/util/io.c:262:socket_connect() Failed to connect to host 127.0.0.1, port 2321: errno 111: Connection refused ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-mssim.so.0 ERROR:tcti:src/tss2-tcti/tctildr-dl.c:254:tctildr_get_default() No standard TCTI could be loaded ERROR:tcti:src/tss2-tcti/tctildr.c:428:Tss2_TctiLdr_Initialize_Ex() Failed to instantiate TCTI ERROR: Could not load tcti, got: "(null)"
若遇到這樣的問題,可以先檢查 /dev/tpmrm0
設備檔的權限與擁有者:
# 檢查 /dev/tpmrm0 設備檔的權限與擁有者 ls -l /dev/tpmrm0
crw-rw---- 1 tss tss 253, 65536 5月 24 07:26 /dev/tpmrm0
確認檔案權限之後,使用上面的 usermod
指令將要存取 /dev/tpmrm0
設備檔的使用者加入該群組(以這裡的例子來說就是 tss
)。
若不想更改使用者的群組設定,也可以直接使用 root
權限存取 /dev/tpmrm0
設備檔。
tpm2-openssl 是一個可以讓 OpenSSL API 存取 TPM 2.0 設備的 provider,適用於 OpenSSL 3.x 版本,如果是 OpenSSL 1.1 的環境,要改用 tpm2-tss-engine。
在 Ubuntu 或 Debian 系列的 Linux 中可用 apt
安裝:
# 安裝 tpm2-openssl 套件
sudo apt install tpm2-openssl
安裝好 tpm2-openssl 之後,就可以在 OpenSSL 中使用 tpm2
這個 provider 了,例如若要以 TPM 產生 8 位元組的隨機資料,並以 16 進位輸出,可以執行:
# 使用 TPM 產生 8 位元組的隨機資料,以 16 進位輸出 openssl rand -provider tpm2 -hex 8
2a0e1fd3e96ad0cb
若要將隨機資料輸出至檔案,可以執行:
# 使用 TPM 產生 8 位元組的隨機資料,輸出至檔案 openssl rand -provider tpm2 -out random.out 8
tpm2-tss 函式庫是 Trusted Computing Group(TCG)TPM2 Software Stack(TSS)的一個實作版本,TSS 從高階到低階,包含了 Feature API(FAPI)、Enhanced System API(ESAPI)、System API(SAPI)、Marshaling/Unmarshaling(MU)、TPM Command Transmission Interface(TCTI)。
在 Ubuntu 或 Debian 系列的 Linux 中,若要使用 tpm2-tss 函式庫,可以使用 apt
安裝:
# 安裝 libtss2-dev 相關開發套件
apt install libtss2-dev
安裝好之後,即可使用 tpm2-tss 函式庫內的 API 進行開發。
以下是使用 Enhanced System API(ESAPI)以 TPM 產生亂數的 C 語言範例:
#include <stdlib.h> #include <stdio.h> #include <tss2/tss2_esys.h> #include <tss2/tss2_rc.h> int main() { ESYS_CONTEXT *context = NULL; // ESYS 初始化 TSS2_RC rc = Esys_Initialize(&context, NULL, NULL); // 檢查 ESYS 初始化是否成功 if (rc != TSS2_RC_SUCCESS) { fprintf(stderr, "Esys_Initialize: %s\n", Tss2_RC_Decode(rc)); return EXIT_FAILURE; } TPM2B_DIGEST *bytes = NULL; // 使用 TPM 產生亂數 rc = Esys_GetRandom(context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, 8, // 產生的亂數資料長度 &bytes); // 檢查 TPM 產生亂數是否成功 if (rc != TSS2_RC_SUCCESS) { fprintf(stderr, "Esys_GetRandom: %s\n", Tss2_RC_Decode(rc)); return EXIT_FAILURE; } // 輸出亂數 for (size_t i = 0; i < bytes->size; i++) { printf("%02x", bytes->buffer[i]); } printf("\n"); Esys_Free(bytes); Esys_Finalize(&context); return EXIT_SUCCESS; }
將上面這段程式碼儲存為 my_random.c
,使用以下 gcc
指令進行編譯:
# 編譯程式 gcc -o my_random my_random.c -ltss2-esys -ltss2-rc
編譯完成後,執行產生的 my_random
程式:
# 執行程式
./my_random
01071414c00a3009