You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用tpm2-tools生成的ctx密钥文件通过go-tpm实现数据签名?

问题

在Ubuntu Linux虚拟机中使用TPM2,已通过tpm2-tools创建临时主密钥的idevid.ctx文件,可通过tpm2-tools正常完成数据签名,但使用github.com/google/go-tpm/tpm2包加载该文件并签名时遇到两个问题:

  1. 直接调用ctx.SavedHandle.KnownName()获取名称时返回nil,触发空指针panic;
  2. 调用ContextLoad加载ctx文件时,报错TPM_RC_SIZE (parameter 1): structure is the wrong size。

相关操作命令

创建密钥命令:

echo -n -e "\x00\x01\x49\x44\x45\x56\x49\x44" > idevid.dat
tpm2_createprimary --hierarchy e --key-algorithm rsa2048 --attributes "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|sign|noda" --key-context idevid.ctx --unique-data idevid.dat --output idevid.pub

tpm2-tools签名命令:

echo "my message" > message.dat
tpm2_sign --key-context idevid.ctx --hash-algorithm sha256 --signature sig.rssa message.dat
tpm2_verifysignature --key-context idevid.ctx --hash-algorithm sha256 --signature sig.rssa --message message.dat

已有的go-tpm连接代码

import (
    "fmt"
    "github.com/google/go-tpm/tpm2/transport"
)

func main() {
    tpmDevice := "/dev/tpmrm0"
    tpm, err := transport.OpenTPM(tpmDevice)
    if err != nil {
        fmt.Printf("Could not connect to TPM: %v", err)
        return
    }
    defer tpm.Close()
}
解决方法

核心问题是tpm2-tools生成的.ctx文件带有自定义头部,而go-tpm的ContextLoad只接受纯TPM2B_CONTEXT_DATA结构,以下是完整的正确流程:

步骤1:读取并处理ctx文件

tpm2-tools生成的ctx文件开头有8字节自定义头部(4字节魔数TPMC+4字节版本号),需先跳过这部分:

import (
    "os"
    "log"
)

// 读取并处理ctx文件
ctxData, err := os.ReadFile("idevid.ctx")
if err != nil {
    log.Fatalf("读取ctx文件失败: %v", err)
}
if len(ctxData) < 8 {
    log.Fatalf("无效的ctx文件: 尺寸过小")
}
// 跳过前8字节头部,保留纯TPM上下文数据
rawContext := ctxData[8:]

步骤2:加载上下文到TPM

使用处理后的原始数据调用ContextLoad,并记得使用后卸载上下文:

import "github.com/google/go-tpm/tpm2"

// 加载上下文到TPM
handle, _, err := tpm2.ContextLoad(tpm, rawContext)
if err != nil {
    log.Fatalf("加载上下文失败: %v", err)
}
// 临时密钥会话结束后卸载上下文(可选,但更规范)
defer func() {
    _, err := tpm2.ContextUnload(tpm, handle)
    if err != nil {
        log.Printf("卸载上下文失败: %v", err)
    }
}()

步骤3:安全获取密钥名称(避免空指针)

不要直接使用SavedHandle,通过TPM读取公钥来获取密钥名称:

// 读取公钥及密钥名称
pub, err := tpm2.ReadPublic(tpm, handle)
if err != nil {
    log.Fatalf("读取公钥失败: %v", err)
}
// pub.Name即为密钥的TPM2B_NAME结构,可用于后续验证或标识

步骤4:完成数据签名

先计算消息哈希,再调用tpm2.Sign完成签名(若密钥有授权需额外处理会话):

import (
    "crypto/sha256"
    "github.com/hashicorp/go-uuid/xdr"
)

// 读取待签名消息
message, err := os.ReadFile("message.dat")
if err != nil {
    log.Fatalf("读取消息文件失败: %v", err)
}

// 计算SHA256哈希
hash := sha256.Sum256(message)

// 执行签名(此处假设密钥无授权,若有授权需传入auth会话)
sig, err := tpm2.Sign(tpm, handle, nil, hash[:], &tpm2.SigScheme{
    Scheme: tpm2.AlgRSASSA,
    Details: &tpm2.SigScheme_RSASSA{
        RSASSA: &tpm2.SigSchemeRSASSA{HashAlg: tpm2.AlgSHA256},
    },
})
if err != nil {
    log.Fatalf("签名失败: %v", err)
}

// 将签名序列化并保存到文件
sigBytes, err := xdr.Marshal(sig)
if err != nil {
    log.Fatalf("序列化签名失败: %v", err)
}
if err := os.WriteFile("sig.go-tpm", sigBytes, 0644); err != nil {
    log.Fatalf("保存签名文件失败: %v", err)
}

关键注意事项

  • 确保go-tpm版本与tpm2-tools版本兼容,避免协议格式差异;
  • 临时主密钥(非持久化)的上下文在TPM重启后会失效,需重新创建;
  • 若密钥设置了授权密码,需使用tpm2.StartAuthSession创建授权会话并传入Sign方法。

内容的提问来源于stack exchange,提问作者Ally

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 20:23:15