基于TPM模拟器对文件进行加解密和Hash验证

这里记录了一次 TPM2.0 模拟器的测试,包括源码安装以及使用程序对文件进行加解密和 Hash 计算验证。
TPM2.0 模拟器是纯软件模拟实现的,只适合用来学习和测试,同时还存在许多限制,与真实的 TPM 模块还有许多差距,要想实现更加接近真实的 TPM 模块,如 Windows 中用来实现 Bitlocker 等,可以在 Vmware 的虚拟机设置中添加可信平台模块(TPM)来实现。但我们这里只是为了学习一下 TPM 使用和一些原理的探究,仅使用 TPM 模拟器即可。

(一)环境搭建与基础配置

安装TPM2.0模拟器

1)安装相关依赖

1
2
3
4
5
6
7
8
9
10
sudo apt update
sudo apt install -y \
autoconf-archive \
libcmocka0 libcmocka-dev \
procps iproute2 \
build-essential git pkg-config gcc libtool automake \
libssl-dev uthash-dev autoconf doxygen \
libjson-c-dev libini-config-dev \
libcurl4-gnutls-dev libgcrypt-dev m4 uuid-dev \
libglib2.0-dev


2)安装 TPM 2.0 模拟器

1
2
3
mkdir ibmtpm && cd ibmtpm
git clone https://github.com/kgoldman/ibmswtpm2.git
cd src/

1
2
sudo make
sudo cp tpm_server /usr/local/bin

增加 tpm-server.service 文件

1
sudo vi /lib/systemd/system/tpm-server.service

然后把下面内容添加进去:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=TPM 2.0 Simulator Server Daemon
Before=tpm2-abrmd.service

[Service]
ExecStart=/usr/local/bin/tpm_server
Restart=always
Environment=PATH=/usr/bin:/usr/local/bin

[Install]
WantedBy=multi-user.target


重新加载服务配置文件:

1
systemctl daemon-reload

启动 TPM 2.0 服务:

1
systemctl start tpm-server.service

测试 TPM 2.0 服务运行情况:

1
service tpm-server status


可以看到,TPM2.0模拟器正在监听2321端口,说明我们安装成功了。

安装Intel TSS2.0软件

(1)首先添加 tss 用户

1
sudo useradd --system --user-group tss


(2)TSS 安装

1
2
3
4
5
#如果链接不是很稳定,可以使用国内镜像$ wget https://ghproxy.com/https://github.com/tpm2-software/tpm2-tss/releases/download/2.1.0/tpm2-tss-2.1.0.tar.gz
#wget https://github.com/tpm2-software/tpm2-tss/releases/download/2.1.0/tpm2-tss-2.1.0.tar.gz
wget https://github.com/tpm2-software/tpm2-tss/releases/download/4.1.3/tpm2-tss-4.1.3.tar.gz
tar zxvf tpm2-tss-4.1.3.tar.gz

1
2
3
cd tpm2-tss-4.1.3/
./configure --enable-unit #--enable-integration

1
2
3
sudo make check
sudo make install
sudo ldconfig



(3)TPM2-abrmd 安装

1
2
3
4
5
6
#wget https://github.com/tpm2-software/tpm2-abrmd/releases/download/2.0.2/tpm2-abrmd-2.0.2.tar.gz
wget https://github.com/tpm2-software/tpm2-abrmd/releases/download/2.4.1/tpm2-abrmd-2.4.1.tar.gz
tar zxvf tpm2-abrmd-2.4.1.tar.gz
cd tpm2-abrmd-2.4.1/
sudo ldconfig
./configure --with-dbuspolicydir=/etc/dbus-1/system.d --with-udevrulesdir=/usr/lib/udev/rules.d --with-systemdsystemunitdir=/usr/lib/systemd/system


1
sudo make install

添加 tpm2_abrmd 服务
复制 com.intel.tss2.Tabrmd.service 到/usr/share/dbus-1/system-services/

1
sudo cp /usr/local/share/dbus-1/system-services/com.intel.tss2.Tabrmd.service /usr/share/dbus-1/system-services

重启 DBUS

1
sudo pkill -HUP dbus-daemon

修改 system tpm2-abrmd.service 服务配置

1
sudo vi /lib/systemd/system/tpm2-abrmd.service

在 ExecStart=/usr/local/sbin/tpm2-abrmd 之后添加:

1
--tcti="libtss2-tcti-mssim.so.0:host=127.0.0.1,port=2321"

注意还要把After和Requires注释掉,否则启动时会因为找不到/dev/tpm0而卡住。

重新加载服务的配置文件:

1
systemctl daemon-reload

启动 tpm2_abrmd 服务并查看其状态

1
2
sudo systemctl start tpm2-abrmd.service
service tpm2-abrmd status


(4)tpm2.0-tools 安装

1
2
3
4
git clone https://github.com/tpm2-software/tpm2-tools.git
cd tpm2-tools
./bootstrap
./configure



执行安装

1
2
sudo make
sudo make install

(5)访问权限设置

1
2
#sudo usermod -a -G tss john
sudo usermod -a -G tss ramoor

增加文件/etc/udev/rules.d/tpm-udev.rules

1
2
3
4
5
# tpm devices can only be accessed by the tss user but the tss
# group members can access tpmrm devices

KERNEL=="tpm[0-9]*", TAG+="systemd", MODE="0660", OWNER="tss"
KERNEL=="tpmrm[0-9]*", TAG+="systemd", MODE="0660", GROUP="tss"


激活规则

1
2
sudo udevadm control --reload-rules
sudo udevadm trigger

重新连接登录系统su - $USER

(6)简单测试
测试 tpm2-tools 工具连接 abrmd 服务是否正常

1
tpm2_getrandom 4 | xxd


打印 PCR 值查看输出是否正常

1
tpm2_pcrread

TPM2.0试用

包括生成密钥、加密解密、签名验证、会话和策略相关操作、NV 索引等。

(1)生成主对象 primary.ctx:

1
tpm2_createprimary -c primary.ctx

(2)生成 RSA 密钥,加密及验证过程
创建 RSA 密匙文件,-C 指定父对象,-G 指定算法,-u 指定公共部分输出文件,-r 指定秘密部分输出文件

1
2
3
4
5
6
tpm2_create -C primary.ctx -Grsa2048 -u key.pub -r key.priv
tpm2_load -C primary.ctx -u key.pub -r key.priv -c key.ctx
echo “my message” > msg.dat
tpm2_rsaencrypt -c key.ctx -o msg.enc msg.dat
tpm2_rsadecrypt -c key.ctx -o msg.ptext msg.enc
cat msg.ptext


(3)用 TPM 2.0 签名,用 OpenSSL 验证
使用 OpenSSL 生成签名用的密钥。

1
2
3
4
5
6
7
8
9
10
11
# 1. 生成 ECC 密钥对
openssl ecparam -name prime256v1 -genkey -noout -out private.ecc.pem
openssl ec -in private.ecc.pem -pubout -out public.ecc.pem

# 2. 准备待签数据
echo "data to sign" > data.in.raw
sha256sum data.in.raw | awk '{print $1}' | xxd -r -p > data.in.digest

# 3. 外部加载 + 签名
tpm2_loadexternal -Q -G ecc -r private.ecc.pem -c key.ctx
tpm2_sign -Q -c key.ctx -g sha256 -d -f plain -o data.out.signed data.in.digest

使用 OpenSSL 验证签名

1
2
3
4
5
openssl dgst -verify public.ecc.pem \
-keyform PEM \
-sha256 \
-signature data.out.signed \
data.in.raw


(4)加解密数据
以 AES 加解密为例,生成 AES 密钥并加载。

1
2
3
4
5
6
7
8
9
10
#生成并加载 AES-128 密钥
tpm2_create -C primary.ctx -Gaes128 -u key.pub -r key.priv
tpm2_load -C primary.ctx -u key.pub -r key.priv -c key.ctx
#加密
echo "my secret" > secret.dat
tpm2_encryptdecrypt -c key.ctx -o secret.enc secret.dat
#解密
tpm2_encryptdecrypt -d -c key.ctx -o secret.dec secret.enc
#验证
cat secret.dec

(二)编写程序实现对secret.txt文件的加解密

1)程序初始化与密钥管理: 使用TPM函数实现TPM设备的连接和密钥创建。
准备测试文件

1
echo "Ramoor's test message" > secret.txt

安装开发库

1
sudo apt install libtss2-dev


编译

1
gcc init.c -o tpm_init -ltss2-esys -ltss2-rc

PS: 由于手写持续报错,所以放弃了,选择 AI 一把梭,但这里需要注意的是,我这里没有解决 TPM 2.0 模拟器上的会话授权问题,因此我选择了使用空授权值简化了问题直接进行了测试。所以想要测试会话授权策略的师傅们最好先测试一下能否正常初始化 Owner 并创建授权会话,之后再根据授权策略思路来编写代码。

初始化实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tss2/tss2_esys.h>
#include <tss2/tss2_rc.h>

#define CHECK_RC(msg) \
if (rc != TSS2_RC_SUCCESS) { \
fprintf(stderr, "%s: 0x%x (%s)\n", (msg), rc, Tss2_RC_Decode(rc)); \
goto cleanup; \
}

/* 用 Esys_Free 释放由 ESAPI 分配的指针 */
#define SAFE_ESYS_FREE(p) do { if ((p) != NULL) { Esys_Free(p); (p) = NULL; } } while (0)

int main(void) {
ESYS_CONTEXT *esys_ctx = NULL;
ESYS_TR primaryHandle = ESYS_TR_NONE; // 主密钥句柄
ESYS_TR aesKeyHandle = ESYS_TR_NONE; // AES密钥句柄
TSS2_RC rc;

// ===================== 1. 连接TPM设备 =====================
rc = Esys_Initialize(&esys_ctx, NULL, NULL);
if (rc != TSS2_RC_SUCCESS) {
fprintf(stderr, "TPM设备连接失败: 0x%x (%s)\n", rc, Tss2_RC_Decode(rc));
fprintf(stderr, "解决:确保资源管理器运行(tpm2-abrmd 或内核RM)。例如:sudo systemctl start tpm2-abrmd\n");
return 1;
}
printf("TPM设备连接成功\n");

// (修改点)设置空口令,用于 Owner 层级与随后创建的对象
TPM2B_AUTH emptyAuth = { .size = 0, .buffer = {0} };
rc = Esys_TR_SetAuth(esys_ctx, ESYS_TR_RH_OWNER, &emptyAuth);
CHECK_RC("设置 ESYS_TR_RH_OWNER 空口令");

// ===================== 2. 创建主密钥(Primary Key) =====================
// 主密钥配置(RSA算法,受限解密,用于存储/解封装子密钥)
TPM2B_PUBLIC primaryPub = {
.size = 0,
.publicArea = {
.type = TPM2_ALG_RSA,
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = (TPMA_OBJECT_USERWITHAUTH |
TPMA_OBJECT_RESTRICTED |
TPMA_OBJECT_DECRYPT |
TPMA_OBJECT_FIXEDTPM |
TPMA_OBJECT_FIXEDPARENT |
TPMA_OBJECT_SENSITIVEDATAORIGIN),
.authPolicy = { .size = 0 },
.parameters.rsaDetail = {
.symmetric = {
.algorithm = TPM2_ALG_AES,
.keyBits.aes = 128,
.mode.aes = TPM2_ALG_CFB,
},
.scheme = { .scheme = TPM2_ALG_NULL },
.keyBits = 2048,
.exponent = 0,
},
.unique.rsa = { .size = 0 }
}
};

TPM2B_SENSITIVE_CREATE primarySens = { .size = 0 };
primarySens.sensitive.userAuth.size = 0; // 空密码

TPM2B_DATA outsideInfo = {.size = 0};
TPML_PCR_SELECTION creationPCR = {.count = 0};

TPM2B_PUBLIC *outPub = NULL;
TPM2B_CREATION_DATA *creationData = NULL;
TPM2B_DIGEST *creationHash = NULL;
TPMT_TK_CREATION *creationTicket = NULL;

// (修改点)这里需要提供至少一个授权会话:ESYS_TR_PASSWORD
rc = Esys_CreatePrimary(esys_ctx,
ESYS_TR_RH_OWNER, // 父句柄(Owner层级)
ESYS_TR_PASSWORD, // 授权会话1(口令,空)
ESYS_TR_NONE, // 会话2
ESYS_TR_NONE, // 会话3
&primarySens,
&primaryPub,
&outsideInfo,
&creationPCR,
&primaryHandle, // 输出:主密钥句柄
&outPub,
&creationData,
&creationHash,
&creationTicket);
CHECK_RC("主密钥创建失败");
printf("主密钥创建成功(句柄:0x%lx)\n", (unsigned long)primaryHandle);

// (修改点)给新创建的 primary 对象设置空口令(用于后续以它为父对象的授权)
rc = Esys_TR_SetAuth(esys_ctx, primaryHandle, &emptyAuth);
CHECK_RC("设置 primaryHandle 空口令");

// 这些返回值若不再使用,稍后统一 Esys_Free
// ===================== 3. 创建AES对称密钥(子密钥) =====================
TPM2B_PUBLIC aesPub = {
.size = 0,
.publicArea = {
.type = TPM2_ALG_SYMCIPHER,
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = (TPMA_OBJECT_USERWITHAUTH |
TPMA_OBJECT_DECRYPT | // 对称 EncryptDecrypt 命令需 DECRYPT 位
TPMA_OBJECT_FIXEDTPM |
TPMA_OBJECT_FIXEDPARENT |
TPMA_OBJECT_SENSITIVEDATAORIGIN),
.authPolicy = { .size = 0 },
.parameters.symDetail = {
.sym = {
.algorithm = TPM2_ALG_AES,
.keyBits.aes = 128,
.mode.aes = TPM2_ALG_CFB,
}
},
.unique.sym = { .size = 0 }
}
};

TPM2B_SENSITIVE_CREATE aesSens = { .size = 0 };
aesSens.sensitive.userAuth.size = 0; // 空口令

TPM2B_PUBLIC *aesOutPub = NULL;
TPM2B_PRIVATE *aesOutPriv = NULL;
TPM2B_CREATION_DATA *aesCreationData = NULL;
TPM2B_DIGEST *aesCreationHash = NULL;
TPMT_TK_CREATION *aesCreationTicket = NULL;

// 第一步:Esys_Create —— 需要对“父对象 primaryHandle”授权
rc = Esys_Create(esys_ctx,
primaryHandle, // 父句柄:主密钥
ESYS_TR_PASSWORD, // (修改点)授权会话1
ESYS_TR_NONE,
ESYS_TR_NONE,
&aesSens,
&aesPub,
&outsideInfo,
&creationPCR,
&aesOutPriv,
&aesOutPub,
&aesCreationData,
&aesCreationHash,
&aesCreationTicket);
CHECK_RC("AES密钥创建失败");

// 第二步:Esys_Load —— 同样需要对“父对象 primaryHandle”授权
rc = Esys_Load(esys_ctx,
primaryHandle,
ESYS_TR_PASSWORD, // (修改点)授权会话1
ESYS_TR_NONE,
ESYS_TR_NONE,
aesOutPriv,
aesOutPub,
&aesKeyHandle); // 输出:AES密钥句柄
CHECK_RC("AES密钥加载失败");

printf("AES密钥创建并加载成功(句柄:0x%lx)\n", (unsigned long)aesKeyHandle);
printf("\n 核心目标达成:TPM连接成功 + 密钥创建成功!\n");

cleanup:
// 释放资源(避免内存泄漏/TPM句柄占用)
if (aesKeyHandle != ESYS_TR_NONE)
Esys_FlushContext(esys_ctx, aesKeyHandle);
if (primaryHandle != ESYS_TR_NONE)
Esys_FlushContext(esys_ctx, primaryHandle);

// (修改点)所有 ESAPI 分配的返回指针统一用 Esys_Free
SAFE_ESYS_FREE(outPub);
SAFE_ESYS_FREE(creationData);
SAFE_ESYS_FREE(creationHash);
SAFE_ESYS_FREE(creationTicket);

SAFE_ESYS_FREE(aesOutPub);
SAFE_ESYS_FREE(aesOutPriv);
SAFE_ESYS_FREE(aesCreationData);
SAFE_ESYS_FREE(aesCreationHash);
SAFE_ESYS_FREE(aesCreationTicket);

if (esys_ctx)
Esys_Finalize(&esys_ctx);

return rc == TSS2_RC_SUCCESS ? 0 : 1;
}

编译执行

1
2
3
gcc tpm_init.c -o tpm_init \
$(pkg-config --cflags --libs tss2-esys tss2-rc)
./tpm_init

2)实现加密功能: 使用TPM函数接口对 secret.txt 进行加密,并保存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
// tpm_encrypt.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>

#include <tss2/tss2_esys.h>
#include <tss2/tss2_rc.h>
#include <tss2/tss2_mu.h>

#define SRK_PERSISTENT_HANDLE 0x81000001u
#define AES_PRIV_PATH "./tpm_aes_key.priv"
#define AES_PUB_PATH "./tpm_aes_key.pub"
#define INPUT_PATH "secret.txt"
#define OUTPUT_PATH "secret.enc"

#define CHUNK (1024u)
#define IV_SIZE 16

#define CHECK_RC(_rc, _msg) \
do { \
if ((_rc) != TSS2_RC_SUCCESS) { \
fprintf(stderr, "❌ %s: 0x%x (%s)\n", (_msg), (_rc), Tss2_RC_Decode(_rc)); \
goto cleanup; \
} \
} while (0)

static int file_read_all(const char *path, uint8_t **buf, size_t *len) {
*buf = NULL; *len = 0;
FILE *f = fopen(path, "rb");
if (!f) return -1;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return -1; }
rewind(f);
uint8_t *tmp = (uint8_t*)malloc((size_t)sz);
if (!tmp) { fclose(f); return -1; }
size_t n = fread(tmp, 1, (size_t)sz, f);
fclose(f);
if (n != (size_t)sz) { free(tmp); return -1; }
*buf = tmp; *len = (size_t)sz;
return 0;
}

static int file_write_all(const char *path, const void *buf, size_t len) {
FILE *f = fopen(path, "wb");
if (!f) return -1;
size_t n = fwrite(buf, 1, len, f);
fclose(f);
return n == len ? 0 : -1;
}

static int save_TPM2B_PUBLIC(const char *path, const TPM2B_PUBLIC *pub) {
uint8_t tmp[4096]; size_t off = 0;
TSS2_RC rc = Tss2_MU_TPM2B_PUBLIC_Marshal(pub, tmp, sizeof(tmp), &off);
if (rc != TSS2_RC_SUCCESS) return -1;
return file_write_all(path, tmp, off);
}

static int save_TPM2B_PRIVATE(const char *path, const TPM2B_PRIVATE *priv) {
uint8_t tmp[4096]; size_t off = 0;
TSS2_RC rc = Tss2_MU_TPM2B_PRIVATE_Marshal(priv, tmp, sizeof(tmp), &off);
if (rc != TSS2_RC_SUCCESS) return -1;
return file_write_all(path, tmp, off);
}

static int load_TPM2B_PUBLIC(const char *path, TPM2B_PUBLIC **out) {
*out = NULL;
uint8_t *buf; size_t len;
if (file_read_all(path, &buf, &len) != 0) return -1;
TPM2B_PUBLIC *obj = calloc(1, sizeof(*obj));
size_t off = 0;
TSS2_RC rc = Tss2_MU_TPM2B_PUBLIC_Unmarshal(buf, len, &off, obj);
free(buf);
if (rc != TSS2_RC_SUCCESS) { free(obj); return -1; }
*out = obj;
return 0;
}

static int load_TPM2B_PRIVATE(const char *path, TPM2B_PRIVATE **out) {
*out = NULL;
uint8_t *buf; size_t len;
if (file_read_all(path, &buf, &len) != 0) return -1;
TPM2B_PRIVATE *obj = calloc(1, sizeof(*obj));
size_t off = 0;
TSS2_RC rc = Tss2_MU_TPM2B_PRIVATE_Unmarshal(buf, len, &off, obj);
free(buf);
if (rc != TSS2_RC_SUCCESS) { free(obj); return -1; }
*out = obj;
return 0;
}

static void set_auth_from_env_or_empty(ESYS_CONTEXT *ctx, ESYS_TR handle) {
TPM2B_AUTH auth = { .size = 0 };
const char *hex = getenv("TPM_OWNER_AUTH_HEX");
if (hex && *hex) {
size_t L = strlen(hex);
uint8_t tmp[64] = {0};
size_t out = 0;
for (size_t i = 0; i + 1 < L && out < sizeof(tmp); i += 2) {
unsigned v; sscanf(hex + i, "%2x", &v); tmp[out++] = (uint8_t)v;
}
auth.size = (UINT16)out;
memcpy(auth.buffer, tmp, out);
}
Esys_TR_SetAuth(ctx, handle, &auth);
}

#pragma pack(push, 1)
typedef struct {
char magic[8]; // "TPMENC1"
uint16_t version; // 1
uint16_t alg; // TPM2_ALG_AES
uint16_t mode; // TPM2_ALG_CFB
uint16_t iv_size; // 16
uint16_t name_size; // TPM2B_NAME size
uint32_t data_size; // 明文长度
} EncHeader;
#pragma pack(pop)

int main(int argc, char **argv) {
int exit_code = 1;

const char *in_path = (argc > 1) ? argv[1] : INPUT_PATH;
const char *out_path = (argc > 2) ? argv[2] : OUTPUT_PATH;

ESYS_CONTEXT *ctx = NULL;
ESYS_TR srk = ESYS_TR_NONE;
ESYS_TR srk_transient = ESYS_TR_NONE;
ESYS_TR aes = ESYS_TR_NONE;

TSS2_RC rc = Esys_Initialize(&ctx, NULL, NULL);
CHECK_RC(rc, "初始化 ESYS 失败");
printf("已连接 TPM\n");

set_auth_from_env_or_empty(ctx, ESYS_TR_RH_OWNER);

// 1) 获取/创建并固化 SRK 到 0x81000001
{
rc = Esys_TR_FromTPMPublic(ctx, SRK_PERSISTENT_HANDLE,
ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
&srk);
if (rc != TSS2_RC_SUCCESS) {
TPM2B_PUBLIC inPublic = {
.size = 0,
.publicArea = {
.type = TPM2_ALG_RSA,
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = (TPMA_OBJECT_USERWITHAUTH |
TPMA_OBJECT_RESTRICTED |
TPMA_OBJECT_DECRYPT |
TPMA_OBJECT_FIXEDTPM |
TPMA_OBJECT_FIXEDPARENT |
TPMA_OBJECT_SENSITIVEDATAORIGIN),
.authPolicy = {.size = 0},
.parameters.rsaDetail = {
.symmetric = {
.algorithm = TPM2_ALG_AES,
.keyBits.aes = 128,
.mode.aes = TPM2_ALG_CFB,
},
.scheme = {.scheme = TPM2_ALG_NULL},
.keyBits = 2048,
.exponent = 0,
},
.unique.rsa = {.size = 0}
}
};
TPM2B_SENSITIVE_CREATE inSensitive = { .size = 0 };
TPM2B_DATA outsideInfo = {.size = 0};
TPML_PCR_SELECTION creationPCR = {.count = 0};
TPM2B_PUBLIC *outPublic = NULL;
TPM2B_CREATION_DATA *creationData = NULL;
TPM2B_DIGEST *creationHash = NULL;
TPMT_TK_CREATION *creationTicket = NULL;

rc = Esys_CreatePrimary(ctx, ESYS_TR_RH_OWNER,
ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE,
&inSensitive, &inPublic,
&outsideInfo, &creationPCR,
&srk_transient, &outPublic,
&creationData, &creationHash, &creationTicket);
CHECK_RC(rc, "CreatePrimary(SRK) 失败");
Esys_TR_SetAuth(ctx, srk_transient, &(TPM2B_AUTH){.size=0});

ESYS_TR newHandle = ESYS_TR_NONE;
rc = Esys_EvictControl(ctx, ESYS_TR_RH_OWNER, srk_transient,
ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE,
SRK_PERSISTENT_HANDLE, &newHandle);
CHECK_RC(rc, "EvictControl(SRK->persistent) 失败");
if (newHandle != ESYS_TR_NONE) Esys_FlushContext(ctx, newHandle);
if (srk_transient != ESYS_TR_NONE) { Esys_FlushContext(ctx, srk_transient); srk_transient = ESYS_TR_NONE; }

rc = Esys_TR_FromTPMPublic(ctx, SRK_PERSISTENT_HANDLE,
ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
&srk);
CHECK_RC(rc, "TR_FromTPMPublic(SRK) 失败");

if (outPublic) Esys_Free(outPublic);
if (creationData) Esys_Free(creationData);
if (creationHash) Esys_Free(creationHash);
if (creationTicket) Esys_Free(creationTicket);
printf("已创建并固化 SRK @ 0x%08x\n", SRK_PERSISTENT_HANDLE);
} else {
printf("发现已存在的 SRK @ 0x%08x\n", SRK_PERSISTENT_HANDLE);
}
Esys_TR_SetAuth(ctx, srk, &(TPM2B_AUTH){.size=0});
}

// 2) 加载或创建 AES-128-CFB 对称密钥(关键:SIGN_ENCRYPT | DECRYPT)
TPM2B_PUBLIC *aesPub = NULL;
TPM2B_PRIVATE *aesPriv = NULL;
{
int have_files = (access(AES_PRIV_PATH, R_OK) == 0) && (access(AES_PUB_PATH, R_OK) == 0);
if (have_files) {
if (load_TPM2B_PUBLIC(AES_PUB_PATH, &aesPub) != 0 ||
load_TPM2B_PRIVATE(AES_PRIV_PATH, &aesPriv) != 0) {
fprintf(stderr, "读取 AES 密钥 blob 失败\n");
goto cleanup;
}
rc = Esys_Load(ctx, srk,
ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE,
aesPriv, aesPub, &aes);
CHECK_RC(rc, "Esys_Load(AES) 失败");
printf("已从本地 blob 加载 AES 密钥\n");
} else {
TPM2B_PUBLIC inPublic = {
.size = 0,
.publicArea = {
.type = TPM2_ALG_SYMCIPHER,
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = (TPMA_OBJECT_USERWITHAUTH |
TPMA_OBJECT_SIGN_ENCRYPT | // ← 新增
TPMA_OBJECT_DECRYPT | // ← 保留
TPMA_OBJECT_FIXEDTPM |
TPMA_OBJECT_FIXEDPARENT |
TPMA_OBJECT_SENSITIVEDATAORIGIN),
.authPolicy = {.size = 0},
.parameters.symDetail = {
.sym = {
.algorithm = TPM2_ALG_AES,
.keyBits.aes = 128,
.mode.aes = TPM2_ALG_CFB,
}
},
.unique.sym = {.size = 0}
}
};
TPM2B_SENSITIVE_CREATE inSensitive = { .size = 0 };
TPM2B_DATA outsideInfo = {.size = 0};
TPML_PCR_SELECTION creationPCR = {.count = 0};

TPM2B_PUBLIC *outPub = NULL;
TPM2B_PRIVATE *outPriv = NULL;
TPM2B_CREATION_DATA *cdata = NULL;
TPM2B_DIGEST *chash = NULL;
TPMT_TK_CREATION *cticket = NULL;

rc = Esys_Create(ctx, srk,
ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE,
&inSensitive, &inPublic, &outsideInfo, &creationPCR,
&outPriv, &outPub, &cdata, &chash, &cticket);
CHECK_RC(rc, "Esys_Create(AES) 失败");

if (save_TPM2B_PUBLIC(AES_PUB_PATH, outPub) != 0 ||
save_TPM2B_PRIVATE(AES_PRIV_PATH, outPriv) != 0) {
fprintf(stderr, "保存 AES 密钥 blob 失败\n");
if (outPriv) Esys_Free(outPriv);
if (outPub) Esys_Free(outPub);
if (cdata) Esys_Free(cdata);
if (chash) Esys_Free(chash);
if (cticket) Esys_Free(cticket);
goto cleanup;
}

rc = Esys_Load(ctx, srk,
ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE,
outPriv, outPub, &aes);
CHECK_RC(rc, "Esys_Load(AES) 失败");

aesPriv = outPriv;
aesPub = outPub;
if (cdata) Esys_Free(cdata);
if (chash) Esys_Free(chash);
if (cticket) Esys_Free(cticket);
printf("已创建 AES 密钥(blob 已保存到本地)\n");
}
Esys_TR_SetAuth(ctx, aes, &(TPM2B_AUTH){.size=0});
}

// 3) 获取 AES Name
TPM2B_NAME *aesName = NULL;
rc = Esys_TR_GetName(ctx, aes, &aesName);
CHECK_RC(rc, "TR_GetName(AES) 失败");

// 4) 生成 IV
TPM2B_DIGEST *randOut = NULL;
rc = Esys_GetRandom(ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
IV_SIZE, &randOut);
CHECK_RC(rc, "GetRandom(IV) 失败");
TPM2B_IV iv = {.size = IV_SIZE};
memcpy(iv.buffer, randOut->buffer, IV_SIZE);
Esys_Free(randOut);

// 5) 读明文、写头部
uint8_t *plain = NULL; size_t plain_len = 0;
if (file_read_all(in_path, &plain, &plain_len) != 0) {
fprintf(stderr, "读取明文失败: %s\n", in_path);
goto cleanup;
}
FILE *fo = fopen(out_path, "wb");
if (!fo) {
fprintf(stderr, "打开输出文件失败: %s\n", out_path);
goto cleanup;
}

EncHeader hdr;
memset(&hdr, 0, sizeof(hdr));
memcpy(hdr.magic, "TPMENC1", 7);
hdr.version = 1;
hdr.alg = TPM2_ALG_AES;
hdr.mode = TPM2_ALG_CFB;
hdr.iv_size = IV_SIZE;
hdr.name_size = aesName->size;
hdr.data_size = (uint32_t)plain_len;
fwrite(&hdr, 1, sizeof(hdr), fo);
fwrite(iv.buffer, 1, IV_SIZE, fo);
fwrite(aesName->name, 1, aesName->size, fo);

// 6) 加密(decrypt = TPM2_NO)
size_t off = 0;
TPM2B_IV iv_in = iv;
while (off < plain_len) {
size_t n = (plain_len - off > CHUNK) ? CHUNK : (plain_len - off);
TPM2B_MAX_BUFFER in = {.size = (UINT16)n};
memcpy(in.buffer, plain + off, n);

TPM2B_MAX_BUFFER *out = NULL;
TPM2B_IV *iv_out = NULL;

rc = Esys_EncryptDecrypt(ctx, aes,
ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE,
TPM2_NO, // encrypt
TPM2_ALG_CFB,
&iv_in,
&in,
&out, &iv_out);
if (rc != TSS2_RC_SUCCESS) {
fprintf(stderr, "EncryptDecrypt 失败: 0x%x (%s)\n", rc, Tss2_RC_Decode(rc));
if (out) Esys_Free(out);
if (iv_out) Esys_Free(iv_out);
fclose(fo);
goto cleanup;
}

fwrite(out->buffer, 1, out->size, fo);
iv_in.size = iv_out->size;
memcpy(iv_in.buffer, iv_out->buffer, iv_out->size);

Esys_Free(out);
Esys_Free(iv_out);
off += n;
}
fclose(fo);
printf("加密完成:%s → %s\n", in_path, out_path);
exit_code = 0;

cleanup:
if (aesName) Esys_Free(aesName);
if (aes != ESYS_TR_NONE) Esys_FlushContext(ctx, aes);
if (srk_transient != ESYS_TR_NONE) Esys_FlushContext(ctx, srk_transient);
if (srk != ESYS_TR_NONE) Esys_TR_Close(ctx, &srk);
if (aesPub) Esys_Free(aesPub);
if (aesPriv) Esys_Free(aesPriv);
if (plain) free(plain);
if (ctx) Esys_Finalize(&ctx);
return exit_code;
}

编译执行

1
2
3
4
5
6
7
8
9
10
11
12
gcc tpm_encrypt.c -o tpm_encrypt \
$(pkg-config --cflags --libs tss2-esys tss2-rc tss2-mu)

gcc tpm_decrypt.c -o tpm_decrypt \
$(pkg-config --cflags --libs tss2-esys tss2-rc tss2-mu)

# 非常重要:删掉旧 blob(老密钥没有 SIGN_ENCRYPT)
rm -f tpm_aes_key.priv tpm_aes_key.pub

# 重新加密/解密
./tpm_encrypt secret.txt secret.enc
./tpm_decrypt secret.enc secret.dec



3)实现解密功能:使用TPM函数接口对 加密后的文件进行解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// tpm_decrypt.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <tss2/tss2_esys.h>
#include <tss2/tss2_rc.h>
#include <tss2/tss2_mu.h>

#define SRK_PERSISTENT_HANDLE 0x81000001u
#define AES_PRIV_PATH "./tpm_aes_key.priv"
#define AES_PUB_PATH "./tpm_aes_key.pub"
#define INPUT_PATH "secret.enc"
#define OUTPUT_PATH "secret.dec"

#define CHUNK (1024u)
#define CHECK_RC(_rc, _msg) \
do { \
if ((_rc) != TSS2_RC_SUCCESS) { \
fprintf(stderr, "❌ %s: 0x%x (%s)\n", (_msg), (_rc), Tss2_RC_Decode(_rc)); \
goto cleanup; \
} \
} while (0)

static int file_read_all(const char *path, uint8_t **buf, size_t *len) {
*buf = NULL; *len = 0;
FILE *f = fopen(path, "rb");
if (!f) return -1;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return -1; }
rewind(f);
uint8_t *tmp = (uint8_t*)malloc((size_t)sz);
if (!tmp) { fclose(f); return -1; }
size_t n = fread(tmp, 1, (size_t)sz, f);
fclose(f);
if (n != (size_t)sz) { free(tmp); return -1; }
*buf = tmp; *len = (size_t)sz;
return 0;
}

static int save_file(const char *path, const void *buf, size_t len) {
FILE *f = fopen(path, "wb");
if (!f) return -1;
size_t n = fwrite(buf, 1, len, f);
fclose(f);
return n == len ? 0 : -1;
}

static int load_TPM2B_PUBLIC(const char *path, TPM2B_PUBLIC **out) {
*out = NULL;
uint8_t *buf; size_t len;
if (file_read_all(path, &buf, &len) != 0) return -1;
TPM2B_PUBLIC *obj = calloc(1, sizeof(*obj));
size_t off = 0;
TSS2_RC rc = Tss2_MU_TPM2B_PUBLIC_Unmarshal(buf, len, &off, obj);
free(buf);
if (rc != TSS2_RC_SUCCESS) { free(obj); return -1; }
*out = obj;
return 0;
}

static int load_TPM2B_PRIVATE(const char *path, TPM2B_PRIVATE **out) {
*out = NULL;
uint8_t *buf; size_t len;
if (file_read_all(path, &buf, &len) != 0) return -1;
TPM2B_PRIVATE *obj = calloc(1, sizeof(*obj));
size_t off = 0;
TSS2_RC rc = Tss2_MU_TPM2B_PRIVATE_Unmarshal(buf, len, &off, obj);
free(buf);
if (rc != TSS2_RC_SUCCESS) { free(obj); return -1; }
*out = obj;
return 0;
}

static void set_auth_from_env_or_empty(ESYS_CONTEXT *ctx, ESYS_TR handle) {
TPM2B_AUTH auth = { .size = 0 };
const char *hex = getenv("TPM_OWNER_AUTH_HEX");
if (hex && *hex) {
size_t L = strlen(hex);
uint8_t tmp[64] = {0};
size_t out = 0;
for (size_t i = 0; i + 1 < L && out < sizeof(tmp); i += 2) {
unsigned v; sscanf(hex + i, "%2x", &v); tmp[out++] = (uint8_t)v;
}
auth.size = (UINT16)out;
memcpy(auth.buffer, tmp, out);
}
Esys_TR_SetAuth(ctx, handle, &auth);
}

#pragma pack(push, 1)
typedef struct {
char magic[8]; // "TPMENC1"
uint16_t version; // 1
uint16_t alg; // TPM2_ALG_AES
uint16_t mode; // TPM2_ALG_CFB
uint16_t iv_size; // 16
uint16_t name_size; // TPM2B_NAME size
uint32_t data_size; // 明文/密文长度
} EncHeader;
#pragma pack(pop)

int main(int argc, char **argv) {
int exit_code = 1;

const char *in_path = (argc > 1) ? argv[1] : INPUT_PATH;
const char *out_path = (argc > 2) ? argv[2] : OUTPUT_PATH;

ESYS_CONTEXT *ctx = NULL;
ESYS_TR srk = ESYS_TR_NONE;
ESYS_TR aes = ESYS_TR_NONE;

TSS2_RC rc = Esys_Initialize(&ctx, NULL, NULL);
CHECK_RC(rc, "初始化 ESYS 失败");
printf("已连接 TPM\n");

set_auth_from_env_or_empty(ctx, ESYS_TR_RH_OWNER);

rc = Esys_TR_FromTPMPublic(ctx, SRK_PERSISTENT_HANDLE,
ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
&srk);
CHECK_RC(rc, "未找到 SRK(0x81000001)。请先运行 tpm_encrypt 生成。");
Esys_TR_SetAuth(ctx, srk, &(TPM2B_AUTH){.size=0});

TPM2B_PUBLIC *aesPub = NULL;
TPM2B_PRIVATE *aesPriv = NULL;
if (load_TPM2B_PUBLIC(AES_PUB_PATH, &aesPub) != 0 ||
load_TPM2B_PRIVATE(AES_PRIV_PATH, &aesPriv) != 0) {
fprintf(stderr, "无法读取 %s / %s\n", AES_PUB_PATH, AES_PRIV_PATH);
goto cleanup;
}
rc = Esys_Load(ctx, srk,
ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE,
aesPriv, aesPub, &aes);
CHECK_RC(rc, "Esys_Load(AES) 失败");
Esys_TR_SetAuth(ctx, aes, &(TPM2B_AUTH){.size=0});

uint8_t *enc; size_t enc_len;
if (file_read_all(in_path, &enc, &enc_len) != 0 || enc_len < sizeof(EncHeader)) {
fprintf(stderr, "读取密文失败: %s\n", in_path);
goto cleanup;
}
size_t off = 0;
EncHeader hdr;
memcpy(&hdr, enc + off, sizeof(hdr)); off += sizeof(hdr);
if (memcmp(hdr.magic, "TPMENC1", 7) != 0 || hdr.version != 1) {
fprintf(stderr, "文件头不匹配\n");
goto cleanup;
}
if (hdr.iv_size != 16) {
fprintf(stderr, "仅支持 IV=16\n");
goto cleanup;
}
if (enc_len < off + hdr.iv_size + hdr.name_size) {
fprintf(stderr, "文件格式错误(长度不足)\n");
goto cleanup;
}

TPM2B_IV iv = {.size = (UINT16)hdr.iv_size};
memcpy(iv.buffer, enc + off, hdr.iv_size); off += hdr.iv_size;

TPM2B_NAME *name_in_file = calloc(1, sizeof(TPM2B_NAME));
name_in_file->size = (UINT16)hdr.name_size;
memcpy(name_in_file->name, enc + off, hdr.name_size); off += hdr.name_size;

TPM2B_NAME *aesName = NULL;
rc = Esys_TR_GetName(ctx, aes, &aesName);
CHECK_RC(rc, "TR_GetName(AES) 失败");
if (aesName->size != name_in_file->size ||
memcmp(aesName->name, name_in_file->name, aesName->size) != 0) {
fprintf(stderr, "加载的 AES 密钥与密文头记录的 Name 不一致\n");
goto cleanup;
}

size_t ct_len = enc_len - off;
uint8_t *plain = (uint8_t*)malloc(ct_len);
if (!plain) { fprintf(stderr, "内存不足\n"); goto cleanup; }

size_t woff = 0;
TPM2B_IV iv_in = iv;
while (ct_len > 0) {
size_t n = (ct_len > CHUNK) ? CHUNK : ct_len;
TPM2B_MAX_BUFFER in = {.size = (UINT16)n};
memcpy(in.buffer, enc + off, n);

TPM2B_MAX_BUFFER *out = NULL;
TPM2B_IV *iv_out = NULL;
rc = Esys_EncryptDecrypt(ctx, aes,
ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE,
TPM2_YES, // decrypt
TPM2_ALG_CFB,
&iv_in,
&in,
&out, &iv_out);
if (rc != TSS2_RC_SUCCESS) {
fprintf(stderr, "EncryptDecrypt 解密失败: 0x%x (%s)\n", rc, Tss2_RC_Decode(rc));
if (out) Esys_Free(out);
if (iv_out) Esys_Free(iv_out);
goto cleanup;
}

memcpy(plain + woff, out->buffer, out->size);
woff += out->size;
off += n;
ct_len -= n;

iv_in.size = iv_out->size;
memcpy(iv_in.buffer, iv_out->buffer, iv_out->size);

Esys_Free(out);
Esys_Free(iv_out);
}

if (save_file(out_path, plain, woff) != 0) {
fprintf(stderr, "写出解密文件失败: %s\n", out_path);
goto cleanup;
}
printf("解密完成:%s → %s\n", in_path, out_path);
exit_code = 0;

cleanup:
if (aes != ESYS_TR_NONE) Esys_FlushContext(ctx, aes);
if (srk != ESYS_TR_NONE) Esys_TR_Close(ctx, &srk);
if (ctx) Esys_Finalize(&ctx);
return exit_code;
}

(三)编写程序实现对secret.txt文件HASH计算和篡改验证

1)计算原始哈希:使用TPM函数接口计算 secret.txt 的SHA256哈希值,并保存。
2)模拟篡改:复制 secret.txt 为一个新文件,并修改其内容,模拟文件被篡改。
3) 计算新哈希:使用TPM函数接口计算被篡改文件的SHA256哈希值,并进行完整性比对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// tpm_hash_verify.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

#include <tss2/tss2_esys.h>
#include <tss2/tss2_rc.h>

#define DEFAULT_INPUT_PATH "secret.txt"
#define DEFAULT_TAMPERED_PATH "secret_tampered.txt"
#define HASH_OUT_SUFFIX ".sha256"

#define FALLBACK_INPUT_LIMIT 1024u // 如果读不到能力,就按常见上限 1024

#define CHECK_RC(_rc, _msg) \
do { \
if ((_rc) != TSS2_RC_SUCCESS) { \
fprintf(stderr, "❌ %s: 0x%x (%s)\n", (_msg), (_rc), Tss2_RC_Decode(_rc)); \
goto cleanup; \
} \
} while (0)

static int file_copy(const char *src, const char *dst) {
FILE *fi = fopen(src, "rb");
if (!fi) return -1;
FILE *fo = fopen(dst, "wb");
if (!fo) { fclose(fi); return -1; }
uint8_t buf[4096];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), fi)) > 0) {
if (fwrite(buf, 1, n, fo) != n) { fclose(fi); fclose(fo); return -1; }
}
fclose(fi);
fclose(fo);
return 0;
}

static int file_append_text(const char *path, const char *text) {
FILE *f = fopen(path, "ab");
if (!f) return -1;
size_t n = fwrite(text, 1, strlen(text), f);
fclose(f);
return n == strlen(text) ? 0 : -1;
}

static int file_read_all(const char *path, uint8_t **buf, size_t *len) {
*buf = NULL; *len = 0;
FILE *f = fopen(path, "rb");
if (!f) return -1;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return -1; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return -1; }
rewind(f);
uint8_t *tmp = (uint8_t*)malloc((size_t)sz);
if (!tmp) { fclose(f); return -1; }
size_t n = fread(tmp, 1, (size_t)sz, f);
fclose(f);
if (n != (size_t)sz) { free(tmp); return -1; }
*buf = tmp; *len = (size_t)sz;
return 0;
}

static void hex_encode(const uint8_t *in, size_t in_len, char *out_hex /* >= 2*in_len+1 */) {
static const char *HEX = "0123456789abcdef";
for (size_t i = 0; i < in_len; ++i) {
out_hex[2*i] = HEX[(in[i] >> 4) & 0xF];
out_hex[2*i + 1] = HEX[in[i] & 0xF];
}
out_hex[2*in_len] = '\0';
}

static char* build_hash_out_path(const char *path) {
size_t L = strlen(path);
size_t S = strlen(HASH_OUT_SUFFIX);
char *out = (char*)malloc(L + S + 1);
if (!out) return NULL;
memcpy(out, path, L);
memcpy(out + L, HASH_OUT_SUFFIX, S + 1);
return out;
}

/* 获取 TPM 的单次输入缓冲能力(TPM2_PT_INPUT_BUFFER)。失败就返回回退值。 */
static size_t get_tpm_input_limit(ESYS_CONTEXT *ctx) {
TPMI_YES_NO more = TPM2_NO;
TPMS_CAPABILITY_DATA *cap = NULL;
TSS2_RC rc = Esys_GetCapability(ctx,
ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
TPM2_CAP_TPM_PROPERTIES,
TPM2_PT_INPUT_BUFFER, 1,
&more, &cap);
if (rc != TSS2_RC_SUCCESS || !cap) {
fprintf(stderr, "⚠️ 获取 TPM 能力失败,使用回退上限 %u 字节\n", (unsigned)FALLBACK_INPUT_LIMIT);
if (cap) Esys_Free(cap);
return FALLBACK_INPUT_LIMIT;
}
size_t limit = FALLBACK_INPUT_LIMIT;
if (cap->capability == TPM2_CAP_TPM_PROPERTIES &&
cap->data.tpmProperties.count >= 1 &&
cap->data.tpmProperties.tpmProperty[0].property == TPM2_PT_INPUT_BUFFER) {
UINT32 val = cap->data.tpmProperties.tpmProperty[0].value;
// 守护一下范围
if (val < 64) limit = 64;
else if (val > 4096) limit = 4096;
else limit = (size_t)val;
}
Esys_Free(cap);
return limit;
}

/* 用 Esys_Hash 一次性计算 SHA-256(文件必须不超过 TPM 单次输入上限) */
static int tpm_sha256_file_oneshot(ESYS_CONTEXT *ctx, const char *path, uint8_t out32[32]) {
// 读取文件
uint8_t *buf = NULL; size_t len = 0;
if (file_read_all(path, &buf, &len) != 0) {
fprintf(stderr, "读取文件失败:%s\n", path);
return -1;
}

// 检查大小
size_t limit = get_tpm_input_limit(ctx);
if (len > limit) {
fprintf(stderr,
" 文件过大(%zu 字节)超过此 TPM 的单次哈希上限(%zu 字节)。\n"
" 由于该设备的 SequenceUpdate 不可用,建议:\n"
" 1) 先用较小文件测试;或\n"
" 2) 升级/更换 TPM/虚拟TPM;或\n"
" 3) 临时采用软件 SHA-256。\n",
len, limit);
free(buf);
return -1;
}

// 组织输入
TPM2B_MAX_BUFFER in = { .size = (UINT16)len };
memcpy(in.buffer, buf, len);

TPM2B_DIGEST *digest = NULL;
TPMT_TK_HASHCHECK *ticket = NULL;

// 直接调用 Esys_Hash(无需授权会话)
TSS2_RC rc = Esys_Hash(ctx,
ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
&in, TPM2_ALG_SHA256, ESYS_TR_RH_NULL,
&digest, &ticket);
free(buf);

if (rc != TSS2_RC_SUCCESS) {
fprintf(stderr, "Esys_Hash 失败: 0x%x (%s)\n", rc, Tss2_RC_Decode(rc));
if (digest) Esys_Free(digest);
if (ticket) Esys_Free(ticket);
return -1;
}

if (!digest || digest->size != 32) {
fprintf(stderr, "哈希结果异常:size=%u\n", digest ? digest->size : 0);
if (digest) Esys_Free(digest);
if (ticket) Esys_Free(ticket);
return -1;
}
memcpy(out32, digest->buffer, 32);
Esys_Free(digest);
if (ticket) Esys_Free(ticket);
return 0;
}

static int save_hash_hex(const char *file_path, const uint8_t hash[32]) {
char *out_path = build_hash_out_path(file_path);
if (!out_path) return -1;

FILE *fo = fopen(out_path, "wb");
if (!fo) { free(out_path); return -1; }

char hex[65];
hex_encode(hash, 32, hex);
fprintf(fo, "%s\n", hex);
fclose(fo);

printf("已保存哈希:%s\n", out_path);
free(out_path);
return 0;
}

int main(int argc, char **argv) {
const char *orig_path = (argc > 1) ? argv[1] : DEFAULT_INPUT_PATH;
const char *tampered_path = (argc > 2) ? argv[2] : DEFAULT_TAMPERED_PATH;

ESYS_CONTEXT *ctx = NULL;
TSS2_RC rc;

// 初始化 ESYS
rc = Esys_Initialize(&ctx, NULL, NULL);
if (rc != TSS2_RC_SUCCESS) {
fprintf(stderr, "初始化 ESYS 失败: 0x%x (%s)\n", rc, Tss2_RC_Decode(rc));
return 1;
}
printf("已连接 TPM(用于 SHA-256 计算)\n");

// 1) 计算原始文件哈希
uint8_t hash_orig[32];
if (tpm_sha256_file_oneshot(ctx, orig_path, hash_orig) != 0) {
fprintf(stderr, "计算原始哈希失败\n");
goto cleanup;
}
{
char hex[65]; hex_encode(hash_orig, 32, hex);
printf("🔹 原始文件哈希 (SHA-256): %s\n", hex);
}
if (save_hash_hex(orig_path, hash_orig) != 0) {
fprintf(stderr, "保存原始哈希到磁盘失败(非致命)\n");
}

// 2) 复制并篡改
if (file_copy(orig_path, tampered_path) != 0) {
fprintf(stderr, "复制文件失败:%s → %s\n", orig_path, tampered_path);
goto cleanup;
}
if (file_append_text(tampered_path, "\n[TAMPERED] this file was modified for integrity test.\n") != 0) {
fprintf(stderr, "篡改写入失败:%s\n", tampered_path);
goto cleanup;
}
printf("已生成并篡改副本:%s\n", tampered_path);

// 3) 计算篡改后哈希并比对
uint8_t hash_tampered[32];
if (tpm_sha256_file_oneshot(ctx, tampered_path, hash_tampered) != 0) {
fprintf(stderr, "计算篡改文件哈希失败\n");
goto cleanup;
}
{
char hex[65]; hex_encode(hash_tampered, 32, hex);
printf("🔹 篡改文件哈希 (SHA-256): %s\n", hex);
}
if (save_hash_hex(tampered_path, hash_tampered) != 0) {
fprintf(stderr, "保存篡改哈希到磁盘失败(非致命)\n");
}

// 比对
if (memcmp(hash_orig, hash_tampered, 32) == 0) {
printf("完整性校验:未发现篡改(哈希一致)\n");
} else {
printf("完整性校验:哈希不一致,文件已被修改!\n");
}

cleanup:
if (ctx) Esys_Finalize(&ctx);
return 0;
}


可以用别的哈希工具测试一下这里计算的结果是否正确,这里用hashmyfiles测试:

发现跟用TPM测试程序算出的一致,符合预期。


基于TPM模拟器对文件进行加解密和Hash验证
http://ramoor.github.io/2025/12/09/基于TPM模拟器对文件进行加解密和Hash验证/
作者
Ramoor
发布于
2025年12月9日
许可协议