一、前言

在国密算法体系中,SM2是基于椭圆曲线密码(ECC)的非对称加密算法,广泛应用于数字签名、密钥交换等领域。在前文中,我们实现了SM2的加解密、签名与验签功能,但缺少密钥生成模块。本文将深入探讨SM2密钥的生成原理,并提供完整的C++实现代码,涵盖密钥生成公私钥转换十六进制编码等核心功能。


二、SM2密钥生成原理

1. 私钥的本质

SM2私钥是一个随机整数 d,需满足:

d∈[1, n−1]

其中 n 是椭圆曲线生成元 G 的阶,对应SM2标准中预定义的256位大数。

2. 公钥的生成

公钥是椭圆曲线上的一个点 Q,由私钥 d 与生成元 G 通过点乘运算得到:

Q=d⋅G

Q 的坐标 (x, y) 以64字节大端序存储。


三、核心代码实现

1. 密钥对生成函数 GenerateKey

void GenerateKey(SM2_KEY& key) {
    SM2_BN d;
    SM2_JACOBIAN_POINT Q;

    // 生成私钥d,确保范围合法
    do {
        if (fn_rand(d) != 1) {
            throw std::runtime_error("Private key generation failed");
        }
    } while (bn_is_zero(d) || bn_cmp(d, SM2_N) >= 0);

    // 计算公钥Q = d*G
    jacobian_point_mul_generator(&Q, d);

    // 转换私钥为字节
    bn_to_bytes(d, key.private_key);

    // 将公钥坐标转为字节存储
    jacobian_point_to_bytes(&Q, (uint8_t*)&key.public_key);
}

关键点:
循环生成:确保私钥不为零且小于 n
点乘优化:使用雅可比坐标提升计算效率。
内存安全:敏感数据在栈中自动清理。

无效
有效
开始
生成随机数d
fn_rand成功?
检查 1< d < n-1
抛出异常
计算公钥 Q = d·G
转换私钥为字节数组
转换公钥坐标到字节
结束

2. 私钥转公钥函数 CalcPubKey

void CalcPubKey(const char pri_key[64], char pub_key[128]) {
    char bin_key[32] = {0};
    size_t out_len = 32;
    // 十六进制转二进制
    SM2::Hex2Bin(pri_key, 64, bin_key, out_len);

    SM2_BN d;
    bn_from_bytes(d, (uint8_t*)bin_key);

    // 计算公钥Q = d*G
    SM2_JACOBIAN_POINT Q;
    jacobian_point_mul_generator(&Q, d);

    // 转换坐标并输出十六进制
    SM2_POINT point;
    jacobian_point_to_bytes(&Q, (uint8_t*)&point);
    
    out_len = 128;
    SM2::Bin2Hex((char*)&point, 64, pub_key, out_len);
}

流程解析:

  1. 解码私钥:将64字符十六进制字符串转为32字节二进制。
  2. 重建公钥:通过点乘运算生成公钥点。
  3. 编码输出:将坐标转为128字符十六进制字符串。
密钥转换
密钥生成
输出
输出
输出
输出
密钥转HEX
私钥推导公钥
key
GenerateKey
私钥:64字符
公钥:128字符
推导公钥

3. 密钥转十六进制 Key2Hex

void Key2Hex(const SM2_KEY& key, char pri_key[64], char pub_key[128]) {
    // 私钥转HEX
    size_t out_len = 64;
    SM2::Bin2Hex((char*)key.private_key, 32, pri_key, out_len);

    // 公钥转HEX
    out_len = 128;
    SM2::Bin2Hex((char*)&key.public_key, 64, pub_key, out_len);
}

输出格式:
私钥:64字符,如 "81EB26E941BB5AF16DF116495F90695272AE2CD63D6C4AE1678418E33730E700"
公钥:128字符,包含 xy 坐标,如 "55B40D0660A35C53..."


四、使用示例

int main() 
{
    SM2 sm2;

    // 生成密钥
    SM2_KEY key;
    char pPriKey[200] = { 0 };
    char pPubKey[200] = { 0 };
    sm2.GenerateKey(key);
    sm2.Key2Hex(key, pPriKey, pPubKey);
    std::cout << "密钥生成成功:\n私钥:" << pPriKey << "\n公钥:" << pPubKey << std::endl;

    memset(pPubKey, 0, sizeof(pPubKey));
    sm2.CalcPubKey(pPriKey, pPubKey);
    std::cout << "私钥计算公钥成功:\n私钥:" << pPriKey << "\n公钥:" << pPubKey << std::endl;

    if (!sm2.InitKey(priKey, pubKey))
    {
        std::cout << "初始化SM2密钥失败" << std::endl;
        return;
    }
    std::cout << "测试数据:" << strlen(pMsg) << "," << pMsg << std::endl;
}

输出示例:

密钥生成成功:
私钥:f24b1939308a8e235cdecb742a9aae924fa9983c58dbc96ae66eba37c634cb8a
公钥:81d02cff7051b8183a4a2adaddf555f10f7aad5f93eb4b5b3bb1c00247201dda2755ac0329bc8aadcab2498473538a85d9d860094e0e9b1f5b9579fc84c118a4
私钥计算公钥成功:
私钥:f24b1939308a8e235cdecb742a9aae924fa9983c58dbc96ae66eba37c634cb8a
公钥:81d02cff7051b8183a4a2adaddf555f10f7aad5f93eb4b5b3bb1c00247201dda2755ac0329bc8aadcab2498473538a85d9d860094e0e9b1f5b9579fc84c118a4

五、安全注意事项

  1. 私钥保护:生成后应立即加密存储,避免内存泄漏。
  2. 随机数质量:使用符合国密标准的密码学安全随机数生成器(如 /dev/urandom)。
  3. 输入校验:在 CalcPubKey 中需验证私钥的合法性。

六、总结

本文完善了SM2算法的密钥管理模块,通过底层椭圆曲线运算实现了密钥生成、转换和推导功能。这些函数为构建完整的SM2应用(如数字证书、安全通信)奠定了基础。在实际应用中,需结合具体场景强化密钥生命周期管理,确保符合国密合规要求。

更多推荐