如何使用tpm2-tools生成的ctx密钥文件通过go-tpm实现数据签名?
问题
在Ubuntu Linux虚拟机中使用TPM2,已通过tpm2-tools创建临时主密钥的idevid.ctx文件,可通过tpm2-tools正常完成数据签名,但使用github.com/google/go-tpm/tpm2包加载该文件并签名时遇到两个问题:
- 直接调用
ctx.SavedHandle.KnownName()获取名称时返回nil,触发空指针panic; - 调用
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
相关产品推荐
相关产品推荐

