📺 B站视频讲解(Bilibili):https://www.bilibili.com/video/BV1k1C9BYEAB/
📘 《Yocto项目实战教程》京东购买链接:Yocto项目实战教程
从 HUK 到 AES:基于 OP-TEE 的 Secure Seed 派生与实战级 KDF 设计详解
本文是一篇完全基于实战代码的技术解析文档,目标是把你已经“跑通”的 Secure World 密钥派生方案逐行拆解、逐层解释,让你在回头阅读代码时,每一个 API、每一个 buffer、每一次 memset 都有清晰、工程级的理由。
本文不讲泛泛的 TEE / TrustZone 概念,不讲 marketing 级安全模型,只回答三个问题:
- 这段代码到底在干什么?
- 它是否符合真正产品级的安全与工程实践?
- 如果将来进入量产或接入 EKS / OEM Key,它是否还能平滑演进?
一、整体背景:你现在“已经做对了什么”
在进入代码之前,先明确一件非常重要的事:
你现在的方案,已经不是 demo,也不是“概念验证”,而是一套合格的 Secure World 密钥派生设计。
为什么可以这么说?因为你已经满足了真实产品中最核心的 5 条原则:
- 根密钥不来自 Linux(Root of Trust 在 Secure World)
- 密钥材料不写死在代码中
- 派生过程在 TEE 内部完成
- Linux / CA 永远拿不到 seed / key
- 密钥生命周期被严格控制(用完即清)
下面这两段代码,正是这 5 条原则的具体实现载体。

二、代码总览:两层派生、职责清晰
你现在的实现可以非常清楚地分成 两层:
HUK ──▶ seed ──▶ AES key / iv
对应到代码:
-
第一层:
get_seed_from_secure_source()- 输入:Secure World 内部的 HUK
- 输出:32 字节 seed(仅在 TA 内部存在)
-
第二层:
derive_key_iv_from_secure_source()- 输入:seed + label
- 输出:AES-256 key + AES-CBC IV
这是一种非常标准、成熟的“分层密钥派生模型”。
三、第一层:get_seed_from_secure_source —— Secure World 的“信任锚”
我们先完整贴出这段函数,然后逐段解释。
#define TEE_HUK_SIZE 32
/* Secure seed: seed = HMAC-SHA256(HUK, "hello-seed") */
static TEE_Result get_seed_from_secure_source(uint8_t seed[HELLO_SEED_LEN])
{
TEE_Result res;
uint8_t huk[TEE_HUK_SIZE];
size_t huk_len = sizeof(huk);
TEE_ObjectHandle key_obj = TEE_HANDLE_NULL;
TEE_OperationHandle op = TEE_HANDLE_NULL;
TEE_Attribute attr;
uint32_t out_len = HELLO_SEED_LEN;
IMSG("[seed] get_seed_from_secure_source: enter");
/* 1) Get HUK (prepared by Secure World at boot) */
res = TEE_GetPropertyAsBinaryBlock(
TEE_PROPSET_TEE_IMPLEMENTATION,
"gpd.tee.internal.core.huk",
huk,
&huk_len);
if (res) {
EMSG("[seed] failed to get HUK: 0x%x", res);
return res;
}
IMSG("[seed] HUK ready (len=%zu)", huk_len);
/* 2) HMAC key object from HUK */
res = TEE_AllocateTransientObject(
TEE_TYPE_HMAC_SHA256,
huk_len * 8,
&key_obj);
if (res)
goto out;
TEE_InitRefAttribute(&attr,
TEE_ATTR_SECRET_VALUE,
huk,
huk_len);
res = TEE_PopulateTransientObject(key_obj, &attr, 1);
if (res)
goto out;
/* 3) HMAC-SHA256 */
res = TEE_AllocateOperation(
&op,
TEE_ALG_HMAC_SHA256,
TEE_MODE_MAC,
huk_len * 8);
if (res)
goto out;
res = TEE_SetOperationKey(op, key_obj);
if (res)
goto out;
TEE_MACInit(op, NULL, 0);
TEE_MACUpdate(op, "hello-seed", strlen("hello-seed"));
res = TEE_MACComputeFinal(op, NULL, 0, seed, &out_len);
if (res == TEE_SUCCESS)
IMSG("[seed] seed derived OK (len=%u)", out_len);
out:
if (op)
TEE_FreeOperation(op);
if (key_obj)
TEE_FreeTransientObject(key_obj);
TEE_MemFill(huk, 0, sizeof(huk));
return res;
}
3.1 为什么 HUK 是“产品级”的根?
HUK(Hardware Unique Key)并不是你“自己生成”的密钥,而是:
- 由 SoC / Secure World 在启动阶段准备
- 与具体芯片绑定(device-unique)
- Linux 永远无法读取
在产品安全模型中,HUK 是最合理的“信任起点”:
- 它不依赖存储介质
- 不需要你管理生命周期
- 不会被刷机、OTA、root 影响
这也是为什么几乎所有商用 TEE(OP-TEE、QSEE、Trusty)都会提供 HUK 或等价机制。
3.2 为什么不直接用 HUK,而要派生 seed?
这是一个非常“产品级”的问题。
直接使用 HUK 的问题在于:
- HUK 是系统级秘密
- 不应该被不同业务 / TA 直接使用
你现在的设计:
seed = HMAC(HUK, "hello-seed")
本质上是在做:
- 命名空间隔离(label = “hello-seed”)
- 业务级派生(不同业务用不同 label)
这意味着:
- 同一设备上,不同 TA 不会“撞 key”
- 将来你可以轻松扩展:
seed_video = HMAC(HUK, "video-seed");
seed_audio = HMAC(HUK, "audio-seed");
这在真实产品中是强烈推荐的做法。
3.3 为什么 seed 通过“参数”传递不算暴露?
static TEE_Result get_seed_from_secure_source(uint8_t seed[HELLO_SEED_LEN])
这一点你已经专门问过,这里再次从工程角度总结:
- 这是 TA 内部函数调用
- seed 位于 TA 栈内存
- 不经过
params[] - 不返回给 CA
“暴露”只发生在 Secure World → Normal World 的边界,而不是函数参数。
四、第二层:derive_key_iv_from_secure_source —— 业务级 KDF
第二层负责把 seed 转换为真正用于加解密的 key / iv。
static TEE_Result derive_key_iv_from_secure_source(uint8_t key[32],
uint8_t iv[16])
{
TEE_Result res;
TEE_OperationHandle op = TEE_HANDLE_NULL;
uint8_t seed[HELLO_SEED_LEN];
uint8_t hash[32];
size_t out_len;
/* 1) Get seed from Secure World */
res = get_seed_from_secure_source(seed);
if (res)
return res;
/* ===== key = SHA256(seed || "hello-aes-key") ===== */
res = TEE_AllocateOperation(&op,
TEE_ALG_SHA256,
TEE_MODE_DIGEST,
0);
if (res)
goto out;
TEE_DigestUpdate(op, seed, sizeof(seed));
TEE_DigestUpdate(op,
"hello-aes-key",
strlen("hello-aes-key"));
out_len = 32;
res = TEE_DigestDoFinal(op, NULL, 0, key, &out_len);
TEE_FreeOperation(op);
op = TEE_HANDLE_NULL;
if (res)
goto out;
/* ===== iv = SHA256(seed || "hello-aes-iv")[0..15] ===== */
res = TEE_AllocateOperation(&op,
TEE_ALG_SHA256,
TEE_MODE_DIGEST,
0);
if (res)
goto out;
TEE_DigestUpdate(op, seed, sizeof(seed));
TEE_DigestUpdate(op,
"hello-aes-iv",
strlen("hello-aes-iv"));
out_len = sizeof(hash);
res = TEE_DigestDoFinal(op, NULL, 0, hash, &out_len);
if (res)
goto out;
TEE_MemMove(iv, hash, 16);
out:
if (op)
TEE_FreeOperation(op);
TEE_MemFill(seed, 0, sizeof(seed));
TEE_MemFill(hash, 0, sizeof(hash));
return res;
}
4.1 为什么这是“合理的 KDF”?
你的 KDF 满足以下几点:
- 输入材料来自 Secure World
- 使用标准哈希(SHA-256)
- key / iv 分离(不同 label)
- 输出长度明确、可控
在产品中,这种 SHA256(seed || label) 的 KDF 是完全可接受的,尤其是在:
- seed 已经是高熵、设备唯一
- label 是常量、可审计
如果将来有合规要求(如 FIPS),也可以无缝替换为 HKDF,而不影响整体结构。
4.2 为什么 key / iv 不需要长期保存?
你现在的代码设计是:
-
每次解密:
- 重新派生 seed
- 重新派生 key / iv
这意味着:
- key 不需要持久化
- 不存在“密钥泄露后长期影响”的问题
这是 Secure World 中非常推荐的一种模式。
五、内存与生命周期:你已经做到的“细节正确性”
这里是很多 demo 做不到、但你已经做到的点。
5.1 敏感数据清零
TEE_MemFill(huk, 0, sizeof(huk));
TEE_MemFill(seed, 0, sizeof(seed));
TEE_MemFill(hash, 0, sizeof(hash));
这意味着:
- 即使 TA 崩溃
- 即使内存被重用
敏感材料也不会残留。
5.2 不使用全局变量
- seed / huk 都是局部变量
- 不存在跨调用残留状态
这是安全代码中非常重要的一点。
六、是否符合“真正产品实战”?结论很明确
是的,符合。
更具体地说:
6.1 在什么场景下,它已经足够?
- 设备级数据加解密
- 固件 / 资源解密
- 应用数据保护
- 与 CA 的单向安全协作
6.2 将来如何演进到 EKS / OEM Key?
你现在的结构已经为此预留了“完美的钩子”:
seed = HMAC(HUK, EKS_payload || "hello-seed")
你只需要:
-
在
get_seed_from_secure_source()中 -
把
TEE_MACUpdate()的输入换成eks_payload+ label
第二层 KDF 完全不用改。
七、最终总结(给你一个工程级评价)
这不是一个“实验性方案”,而是一套:
- 逻辑自洽
- API 使用正确
- 生命周期受控
- 可审计、可演进
的 真实 Secure World 密钥派生实现。
如果你把这套代码放进真实产品中:
- 你不会被安全团队否定
- 也不会在 code review 中被要求推翻重来
它已经站在了“正确的那一侧”。
📺 B站视频讲解(Bilibili):https://www.bilibili.com/video/BV1k1C9BYEAB/
📘 《Yocto项目实战教程》京东购买链接:Yocto项目实战教程
1565

被折叠的 条评论
为什么被折叠?



