如何将TPM特定的TPMT_PUBLIC数据结构转换为OpenSSL的EVP_PKEY格式ECC公钥?
如何将TPM特定的TPMT_PUBLIC数据结构转换为OpenSSL的EVP_PKEY格式ECC公钥?
我刚好处理过类似的需求,要把TPM返回的TPMT_PUBLIC结构里的ECC公钥转成OpenSSL能直接用的EVP_PKEY,下面是一步步的实现思路和代码示例,亲测可行。
核心思路拆解
要完成转换,核心是从TPMT_PUBLIC里提取ECC公钥的三个关键信息:ECC曲线类型、公钥的x坐标、公钥的y坐标,再用OpenSSL的libcrypto API把这些信息组装成EVP_PKEY。
步骤1:确认公钥类型是ECC
首先要检查TPMT_PUBLIC的type字段是否为TPM_ALG_ECC,如果不是的话,说明这不是ECC公钥,直接跳过后续步骤。
步骤2:映射TPM曲线ID到OpenSSL的NID
TPM的ECC曲线由TPMT_PUBLIC->parameters.eccDetail.curveID(TPMS_ECC_PARMS结构里的curveID)定义,我们需要把它转换成OpenSSL识别的NID(数值标识符)。常见的映射关系如下:
TPM_ECC_NIST_P256→NID_X9_62_prime256v1TPM_ECC_NIST_P384→NID_secp384r1TPM_ECC_NIST_P521→NID_secp521r1
如果你的TPM支持其他曲线,需要查OpenSSL的对应NID补充映射。
步骤3:提取公钥的x、y坐标
TPMT_PUBLIC的publicValue.ecc是TPMS_ECC_POINT结构,里面的x和y都是TPM2B_ECC_PARAMETER类型——这是一个带长度字段的字节数组。我们需要取出x.t.buffer和y.t.buffer里的原始字节数据(TPM存储的坐标是大端字节序,刚好符合OpenSSL的要求),长度对应x.t.size和y.t.size。
步骤4:用OpenSSL API组装EVP_PKEY
下面是具体的C代码实现,包含错误处理和内存管理:
#include <openssl/evp.h> #include <openssl/ec.h> #include <openssl/bn.h> #include <tss2/tss2_tpm2_types.h> EVP_PKEY* tpmt_public_to_evp_pkey(const TPMT_PUBLIC* tpm_public) { if (!tpm_public || tpm_public->type != TPM_ALG_ECC) { fprintf(stderr, "输入不是ECC类型的公钥\n"); return NULL; } // 1. 映射TPM曲线ID到OpenSSL NID int nid = -1; switch (tpm_public->parameters.eccDetail.curveID) { case TPM_ECC_NIST_P256: nid = NID_X9_62_prime256v1; break; case TPM_ECC_NIST_P384: nid = NID_secp384r1; break; case TPM_ECC_NIST_P521: nid = NID_secp521r1; break; // 其他曲线请自行添加映射 default: fprintf(stderr, "不支持该ECC曲线\n"); return NULL; } // 2. 创建EC_KEY结构 EC_KEY* ec_key = EC_KEY_new_by_curve_nid(nid); if (!ec_key) { fprintf(stderr, "创建EC_KEY失败\n"); return NULL; } const EC_GROUP* group = EC_KEY_get0_group(ec_key); if (!group) { fprintf(stderr, "获取EC_GROUP失败\n"); EC_KEY_free(ec_key); return NULL; } // 3. 转换x、y字节数组为BIGNUM BIGNUM* x = BN_bin2bn(tpm_public->publicValue.ecc.x.t.buffer, tpm_public->publicValue.ecc.x.t.size, NULL); BIGNUM* y = BN_bin2bn(tpm_public->publicValue.ecc.y.t.buffer, tpm_public->publicValue.ecc.y.t.size, NULL); if (!x || !y) { fprintf(stderr, "坐标转BIGNUM失败\n"); BN_free(x); BN_free(y); EC_KEY_free(ec_key); return NULL; } // 4. 创建EC_POINT并设置坐标 EC_POINT* point = EC_POINT_new(group); if (!point) { fprintf(stderr, "创建EC_POINT失败\n"); BN_free(x); BN_free(y); EC_KEY_free(ec_key); return NULL; } if (!EC_POINT_set_affine_coordinates_GFp(group, point, x, y, NULL)) { fprintf(stderr, "设置仿射坐标失败\n"); EC_POINT_free(point); BN_free(x); BN_free(y); EC_KEY_free(ec_key); return NULL; } // 5. 将EC_POINT关联到EC_KEY if (!EC_KEY_set_public_key(ec_key, point)) { fprintf(stderr, "给EC_KEY设置公钥失败\n"); EC_POINT_free(point); BN_free(x); BN_free(y); EC_KEY_free(ec_key); return NULL; } // 6. 转换EC_KEY为EVP_PKEY EVP_PKEY* evp_pkey = EVP_PKEY_new(); if (!evp_pkey || !EVP_PKEY_set1_EC_KEY(evp_pkey, ec_key)) { fprintf(stderr, "EC_KEY转EVP_PKEY失败\n"); EVP_PKEY_free(evp_pkey); EC_POINT_free(point); BN_free(x); BN_free(y); EC_KEY_free(ec_key); return NULL; } // 释放中间结构(EVP_PKEY会持有EC_KEY的引用,所以EC_KEY可以释放) EC_POINT_free(point); BN_free(x); BN_free(y); EC_KEY_free(ec_key); return evp_pkey; }
注意事项
- 内存管理:使用完
EVP_PKEY后,记得调用EVP_PKEY_free(evp_pkey)释放内存,避免泄漏。 - 曲线兼容性:确保你的OpenSSL版本支持TPM使用的ECC曲线,有些小众曲线可能需要额外编译OpenSSL支持。
- 错误处理:代码里的每个API调用都加了错误检查,实际项目中可以根据需求调整错误日志的输出方式。
备注:内容来源于stack exchange,提问作者user24805910
相关产品推荐
相关产品推荐

