4. SM2 签名算法在 TEE 中的实现
好,我们进入正题。SM2 签名在 TEE 里跑,跟普通 Linux 应用层签名最大的区别在哪?说白了,就是私钥的“不可见性”。在普通环境里,私钥要么存在文件里,要么存在内存里,总有办法把它 dump 出来。但在 TEE 里,私钥一旦生成,就永远待在安全世界里,REE 侧只能通过特定的接口去“请它签名”,但永远看不到私钥本身。
我刚开始做这个方案的时候,也犯过嘀咕:不就是调个签名接口吗,有什么难的?后来踩了几个坑才明白,TEE 里的签名流程,从数据流入到签名流出,每一步都有讲究。下面我把整个链路拆开来讲。
4.1 签名流程:从 REE 到 TEE 的完整调用
一个完整的 SM2 签名流程,大致分这么几步:
- REE 应用发起签名请求——应用层准备好待签名的数据(比如一段交易报文),通过 TEE Client API 发起调用。
- 数据传入 TEE——数据通过共享内存或安全通道传入 TEE 侧。这里要注意,如果数据本身是敏感信息,建议在 REE 侧先做一次加密,到 TEE 内部再解密。
- TEE 内部执行 SM2 签名——TEE 中的可信应用(TA)收到请求后,调用密码学库的 SM2 签名函数,使用存储在安全存储中的私钥进行签名。
- 签名结果返回 REE——签名结果(r, s)通过安全通道返回给 REE 应用。
- REE 应用使用签名结果——应用拿到签名后,可以将其附加到原始数据中,发送给验证方。
嗯,这里要注意一个细节:待签名数据通常需要先做一次哈希。SM2 签名算法本身是对消息的哈希值进行签名,而不是对原始消息直接签名。这个哈希过程可以在 REE 侧做,也可以在 TEE 侧做。我个人建议在 TEE 内部做哈希,原因很简单——防止 REE 侧篡改数据。
4.2 TEE 内部调用签名接口
在 TEE 内部,SM2 签名的实现通常依赖密码学库。以 OP-TEE 为例,它内部集成了 libtomcrypt 或 mbed TLS,我们可以直接调用封装好的接口。
下面是一个简化的 TA 端签名代码示例:
// TA 端 SM2 签名接口示例
TEE_Result sm2_sign(uint8_t *data, size_t data_len,
uint8_t *signature, size_t *sig_len) {
// 1. 计算 SM3 哈希
uint8_t hash[32];
sm3_hash(data, data_len, hash);
// 2. 从安全存储加载私钥
sm2_private_key_t priv_key;
if (load_private_key(&priv_key) != TEE_SUCCESS) {
return TEE_ERROR_GENERIC;
}
// 3. 执行 SM2 签名
if (sm2_sign_internal(&priv_key, hash, sizeof(hash),
signature, sig_len) != 0) {
return TEE_ERROR_GENERIC;
}
return TEE_SUCCESS;
}
你看,代码逻辑并不复杂。但有几个关键点我得提醒你:
- 私钥加载:私钥必须从 TEE 的安全存储中读取,不能从 REE 侧传入。安全存储通常由 TEE 内核提供,数据在写入时会加密,读取时自动解密。
- 内存保护:私钥在 TA 内存中应该是临时存在的,签名完成后立即擦除。我习惯用
memset_s或平台提供的安全清零函数,防止编译器优化掉。 - 错误处理:任何一步失败,都不能返回部分签名结果。否则可能泄露私钥信息。
4.3 签名验证的完整链路
签名验证通常发生在 REE 侧或远端服务器上。验证方需要知道签名者的公钥,以及原始消息。验证流程如下:
- 获取公钥:公钥可以从 TEE 中导出,但导出过程需要授权(比如用户确认或设备解锁)。公钥本身不敏感,但需要保证其真实性。
- 计算消息哈希:使用 SM3 对原始消息计算哈希值。
- 执行 SM2 验签:使用公钥、哈希值和签名值 (r, s) 进行验证。
- 输出验证结果:成功或失败。
这里有一个常见的误解:验签必须在 TEE 里做吗?不一定。验签只需要公钥,公钥是公开的,所以在 REE 侧验签是安全的。但如果你对 REE 侧的安全性不放心,或者验签逻辑本身需要保护(比如防止被绕过),也可以把验签放到 TEE 里做。
下面这张图展示了完整的签名与验证链路:
4.4 关键安全考量
讲完了流程,我再补充几个实际项目中容易踩的坑:
| 安全风险 | 描述 | 应对措施 |
|---|---|---|
| 重放攻击 | 攻击者截获签名结果,重复使用 | 在待签名数据中加入时间戳或随机数(nonce) |
| 侧信道攻击 | 通过功耗、电磁辐射等物理信息窃取私钥 | 使用恒定时间算法,避免分支依赖于私钥值 |
| 私钥泄露 | 私钥在 TEE 内部被非法读取 | 严格限制 TA 的访问权限,使用安全存储加密 |
| 数据篡改 | REE 侧在传入数据前篡改内容 | 在 TEE 内部计算哈希,或使用 MAC 保护数据完整性 |
最后说一句,SM2 签名在 TEE 里的实现,本质上是一个信任链的问题。TEE 提供了硬件级别的隔离,但上层应用怎么用好这个隔离,才是真正的考验。你想想看,如果私钥保护得再好,但签名接口被随意调用,那跟裸奔有什么区别?所以,权限控制和调用频率限制也是方案设计里不可忽视的一环。