OpenSSL自1.1.1版本开始增加了对国密算法(SM2/SM3/SM4)的支持,这里通过调研OpenSSL(https://github.com/openssl/openssl/blob/master/)中的相应实现源码,总结其中使用到的主要基础函数,以帮助我们梳理其主要实现原理。
这里的所谓基础函数是相对的,其内部实现可能包含多种更细粒度的函数,这里只讨论容易理解的粒度,并且只关注反映相应主要逻辑原理的函数,忽略其它如内存管理、序列化/反序列化以及数据转换等相关基础函数。
TL;DR
对所用到基础函数的简单统计如下:
函数名 | 功能 | 使用场景 |
EC_GROUP_new_by_curve_name_ex | 生成群数据信息 | SM2 |
BN_priv_rand_range_ex | 随机数生成 | SM2 |
EC_POINT_mul | 椭圆曲线点乘 | SM2 |
EC_POINT_get_affine_coordinates | 获取椭圆曲线点坐标值 | SM2 |
ㅤ | ㅤ | ㅤ |
SM2
SM2涉及基于椭圆曲线的公钥密码算法,包括非对称密钥生成、加密、解密、签名及验签等功能。
密钥对生成
提供给使用者的主要接口函数:
// openssl/ec.h EC_KEY *EC_KEY_new_by_curve_name(int nid)
// openssl/ec.h int EC_KEY_generate_key(EC_KEY *eckey)
其内部实现主要涉及对应群(Group)数据的生成,满足特定取值范围(阶order)的私钥(大整数)生成,以及特定椭圆曲线上公钥(曲线上点)的生成。
基础函数:
- 群数据生成(
EC_GROUP_new_by_curve_name_ex
):生成密钥数据结构实例时需要生成对应的群数据(一般是已经硬编码定义好的),包括曲线参数如p, a, b, , , order等。
- 随机数生成(
BN_priv_rand_range_ex
):用于私钥生成,得到满足特定范围的随机数。
- 椭圆曲线点乘(
EC_POINT_mul
→ecp_sm2p256_points_mul
→ecp_sm2p256_point_G_mul_by_scalar
):用于公钥生成,计算出对应的椭圆曲线点。
目前没看懂其内部运算原理,涉及汇编代码。
参考代码:
加解密
提供给使用者的主要接口函数:
// crypto/sm2.h int ossl_sm2_encrypt(const EC_KEY *key, const EVP_MD *digest, const uint8_t *msg, size_t msg_len, uint8_t *ciphertext_buf, size_t *ciphertext_len); int ossl_sm2_decrypt(const EC_KEY *key, const EVP_MD *digest, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t *ptext_buf, size_t *ptext_len);
其加解密属于ECIES(椭圆曲线混合加密),即结合使用非对称密钥算法和对称加密算法对原数据进行加解密。除了用户可见的作为输入参数的非对称密钥外,在其加密的执行过程中还会生成一对新的一次性非对称密钥,并与用户输入的原密钥进行密钥交换衍生出对称密钥,该对称密钥用于对原数据进行对称加密。
因此该过程同样涉及上述密钥对生成过程所需操作及基础函数的调用,不过这其中并不需要重新生成群数据而是直接使用用户输入的原对称密钥的已有群数据;此外根据SM2加密算法标准,最后得到的加密结果需要包含对原数据以及密钥交换生成点的坐标值进行SM3哈希计算得到的摘要信息,因此还包括了哈希计算操作(此外衍生对称密钥时所用的密钥衍生函数内部也有哈希操作)。
加密
基础函数/操作:
- 随机数生成(
BN_priv_rand_range_ex
):
用于进行加密时一次性密钥对中的私钥的生成,
BN_priv_rand_range_ex(k, order, 0, ctx)
- 椭圆曲线点乘(
EC_POINT_mul
):
一方面用于一次性密钥对中的公钥的生成,
// k为一次性密钥对中的私钥 EC_POINT_mul(group, kG, k, NULL, NULL, ctx)
另一方面用于一次性密钥对和用户原密钥对之间的密钥交换,
// P为用户输入密钥对中的公钥,k为一次性密钥对中的私钥 EC_POINT_mul(group, kP, NULL, P, k, ctx)
- 获取椭圆曲线点坐标(
EC_POINT_get_affine_coordinates
):
获取通过椭圆曲线点乘得到的椭圆曲线点的坐标值(x, y),主要针对一次性密钥对中的公钥
kG
和密钥交换生成点kP
。其中一次性密钥对中公钥
kG
的坐标值将被作为加密结果的其中一个字段,EC_POINT_get_affine_coordinates(group, kG, x1, y1, ctx)
而密钥交换得到的点
kP
的坐标用于进一步衍生出对称密钥,EC_POINT_get_affine_coordinates(group, kP, x2, y2, ctx)
- 密钥衍生(
ossl_ecdh_kdf_X9_63
):
用于衍生出对称密钥,该对称密钥长度和原数据长度一致,
ossl_ecdh_kdf_X9_63(msg_mask, msg_len, x2y2, 2 * field_size, NULL, 0, digest, libctx, propq)
- 亦或位操作(
^
):
直接使原数据和衍生密钥进行亦或位操作以达到加密效果,
// msg_mask为衍生密钥,也用于存储加密后的数据,msg为原数据 for (i = 0; i != msg_len; ++i) msg_mask[i] ^= msg[i];
- 哈希计算:(
EVP_MD_fetch
,EVP_DigestInit
,EVP_DigestUpdate
,EVP_DigestFinal
):
对原数据和密钥交换生成点的坐标值进行哈希计算,
fetched_digest = EVP_MD_fetch(libctx, EVP_MD_get0_name(digest), propq) EVP_DigestInit(hash, fetched_digest) EVP_DigestUpdate(hash, x2y2, field_size) EVP_DigestUpdate(hash, msg, msg_len) EVP_DigestUpdate(hash, x2y2 + field_size, field_size) EVP_DigestFinal(hash, C3, NULL)
解密
基础函数/操作:
- 设置椭圆曲线点坐标(
EC_POINT_set_affine_coordinates
):
用于根据输入密文中的相关字段,还原一次性密钥对的公钥
C1
,EC_POINT_set_affine_coordinates(group, C1, sm2_ctext->C1x, sm2_ctext->C1y, ctx)
- 椭圆曲线点乘(
EC_POINT_mul
):
用于使用户输入密钥对和一次性密钥对进行密钥交换,
// C1为一次性密钥对中的公钥,也用于存储密钥交换生成点,key为用户输入密钥对 EC_POINT_mul(group, C1, NULL, C1, EC_KEY_get0_private_key(key), ctx)
- 获取椭圆曲线点坐标(
EC_POINT_get_affine_coordinates
):
获取密钥交换生成点的坐标,以进一步衍生出对称密钥,
EC_POINT_get_affine_coordinates(group, C1, x2, y2, ctx)
- 密钥衍生(
ossl_ecdh_kdf_X9_63
):
用于衍生出对称密钥,
ossl_ecdh_kdf_X9_63(msg_mask, msg_len, x2y2, 2 * field_size, NULL, 0, digest, libctx, propq)
- 亦或位操作(
^
):
直接将被加密数据和衍生密钥进行亦或位操作以达到解密效果,
// C2为被加密的数据,msg_mask为衍生密钥 for (i = 0; i != msg_len; ++i) ptext_buf[i] = C2[i] ^ msg_mask[i];
- 哈希计算:
对解密后的数据和密钥交换生成点的坐标值进行哈希计算,以与密文信息中的相应字段比对,
EVP_DigestInit(hash, digest) EVP_DigestUpdate(hash, x2y2, field_size) EVP_DigestUpdate(hash, ptext_buf, msg_len) EVP_DigestUpdate(hash, x2y2 + field_size, field_size) EVP_DigestFinal(hash, computed_C3, NULL)
参考代码:
签名验签
提供给使用者的主要接口函数:
// crypto/sm2.h ECDSA_SIG *ossl_sm2_do_sign(const EC_KEY *key, const EVP_MD *digest, const uint8_t *id, const size_t id_len, const uint8_t *msg, size_t msg_len) int ossl_sm2_do_verify(const EC_KEY *key, const EVP_MD *digest, const ECDSA_SIG *sig, const uint8_t *id, const size_t id_len, const uint8_t *msg, size_t msg_len)
根据SM2签名算法标准,其算法过程主要涉及随机数生成、椭圆曲线点乘、大整数运算以及哈希计算等操作。
签名
基础函数:
- 用户标识哈希计算(
ossl_sm2_compute_z_digest
):
根据SM2签名算法标准,需要对签名用户的标识信息、椭圆曲线参数和公钥点坐标值等进行哈希计算,得到摘要信息
Z
,该函数内部使用了更细粒度的哈希计算函数,fetched_digest = EVP_MD_fetch(libctx, EVP_MD_get0_name(digest), propq) ossl_sm2_compute_z_digest(z, fetched_digest, id, id_len, key)
- 待签名数据哈希计算:
对前一步得到的用户标识相关摘要信息
Z
以及待签名数据msg
进一步进行哈希计算,最后结果Z
会被转为大整数e
EVP_DigestInit(hash, fetched_digest) EVP_DigestUpdate(hash, z, md_size) EVP_DigestUpdate(hash, msg, msg_len) EVP_DigestFinal(hash, z, NULL) e = BN_bin2bn(z, md_size, NULL)
- 随机数生成(
BN_priv_rand_range_ex
):
生成随机数
k
以进一步计算椭圆曲线点 ,BN_priv_rand_range_ex(k, order, 0, ctx)
- 椭圆曲线点乘(
EC_POINT_mul
):
根据随机数
k
和椭圆曲线上生成点Generator
计算出对应椭圆曲线上的结果点,EC_POINT_mul(group, kG, k, NULL, NULL, ctx)
- 获取椭圆曲线点坐标(
EC_POINT_get_affine_coordinates
):
获取由随机数
k
生成的椭圆曲线点的x
轴坐标,用于下一步计算EC_POINT_get_affine_coordinates(group, kG, x1, NULL, ctx)
- 大整数模加(
BN_mod_add
):
计算出签名结果中的
r
,BN_mod_add(r, e, x1, order, ctx)
- 大整数加(
BN_add
):
计算签名结果中的
s
,// dA为用户输入私钥 BN_add(s, dA, BN_value_one())
- 取倒数(
ossl_ec_group_do_inverse_ord
)
计算签名结果中的
s
,ossl_ec_group_do_inverse_ord(group, s, s, ctx)
- 大整数模乘(
BN_mod_mul
):
计算签名结果中的
s
,BN_mod_mul(tmp, dA, r, order, ctx)
- 大整数减(
BN_sub
):
计算签名结果中的
s
,BN_sub(tmp, k, tmp)
- 大整数模乘(
BN_mod_mul
):
计算签名结果中的
s
,BN_mod_mul(s, s, tmp, order, ctx)
验签
基础函数:
- 用户标识哈希计算(
ossl_sm2_compute_z_digest
):
与签名时所需调用一致
- 待验签数据哈希计算:
与签名时对待签名数据进行哈希计算所需调用一致
- 大整数模加(
BN_mod_add
):
根据待验证的签名结果
(r,s)
,计算BN_mod_add(t, r, s, order, ctx)
- 椭圆曲线点乘(
EC_POINT_mul
):
通过椭圆曲线点乘(这里根据输入参数可发现其函数内部在运行时还包含了一次椭圆曲线点加),计算
// pt存储计算结果点,t=r+s EC_POINT_mul(group, pt, s, EC_KEY_get0_public_key(key), t, ctx)
- 获取椭圆曲线点坐标(
EC_POINT_get_affine_coordinates
):
获取上一步计算出的椭圆曲线结果点的
x
轴坐标值,EC_POINT_get_affine_coordinates(group, pt, x1, NULL, ctx)
- 大整数模加(
BN_mod_add
):
将上一步得到的椭圆曲线点
x
轴坐标值和待验签数据哈希值相加计算,BN_mod_add(t, e, x1, order, ctx)
- 比对结果(
BN_cmp
):
上一步的结果和待验签信息中的
r
进行对比,判断验签是否通过// r == e + x1 BN_cmp(r, t) == 0
参考代码:
SM3
SM3涉及计算数据的摘要信息,也可称为计算杂凑值或计算哈希值。
提供给使用者的主要接口函数:
// openssl/evp.h EVP_MD_CTX *EVP_MD_CTX_new(void) EVP_MD *EVP_sm3(void) int EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type) int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt) int EVP_DigestFinal(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s)
基础函数:
- 加载初始值(
ossl_sm3_init
):
- 添加原数据(
ossl_sm3_update
即HASH_UPDATE
):
- 生成杂凑值(
ossl_sm3_final
即HASH_FINAL
):
参考代码: