使用 openssl 进行椭圆曲线加解密(SM2)
目录
在之前一篇博客 ECC(Elliptic Curves Cryptography) 椭圆曲线加密原理 简单地阐述了 ECC 加解密的原理。这本篇博客中接着来聊一聊如何使用 OPENSSL 来进行 ECC 加解密。
首先需要明确一点的是:ECC 本身并没有定义一套加解密的方法,它主要作用于密钥交换(ECDHE),与签名认证(ECDSA). 不过后来中国工程师设计定义了一套加密方法,并于近年得到了世界的认可,这就是中国商用(国家标准) SM2 椭圆曲线公钥密码算法
。
一. 基本概念
其实在 openssl 中,椭圆曲线分两种形式,一种是之前讲到的质数域上的椭圆曲线 ,将其称为 $F_p$ 其方程为:
$$y^2 \ \mod \ p = x^3 + ax + b \ \mod \ p$$
另一种是二进制域,称为 $F_{2^m}$, 其方程为:
$$y^2 + xy = x^3 + ax^2 + b ,\ (b != 0)$$
在这里只讨论 $F_p$ 相关的内容。
椭圆曲线上的点
椭圆曲线上的点使用 EC_POINT
来表示, 它定义在 ec_locl.h
:
1 2 3 4 5 6 7 |
struct ec_point_st { // ... BIGNUM *X; BIGNUM *Y; BIGNUM *Z; // ... }; |
EC_POINT 的操作定义在 ec.h
中,有以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//r = a + b int EC_POINT_add(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, const EC_POINT *b, BN_CTX *ctx); //r = 2a int EC_POINT_dbl(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, BN_CTX *ctx); //a = 1/a int EC_POINT_invert(const EC_GROUP *group, EC_POINT *a, BN_CTX *ctx); //判断 a 是否在无穷远 int EC_POINT_is_at_infinity(const EC_GROUP *group, const EC_POINT *p); //判断 a 是否在 group 的曲线上 int EC_POINT_is_on_curve(const EC_GROUP *group, const EC_POINT *point, BN_CTX *ctx); // compare a,b int EC_POINT_cmp(const EC_GROUP *group, const EC_POINT *a, const EC_POINT *b, BN_CTX *ctx); int EC_POINT_make_affine(const EC_GROUP *group, EC_POINT *point, BN_CTX *ctx); int EC_POINTs_make_affine(const EC_GROUP *group, size_t num, EC_POINT *points[], BN_CTX *ctx); // r = group的基点 * n + q * m 。如果 n = NULL, 则r = q * m int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx); // r = 基点 * n = q[0] * m[*] + ... + q[num-1] * m[num -1] int EC_POINTs_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, size_t num, const EC_POINT *p[], const BIGNUM *m[], BN_CTX *ctx); int EC_GROUP_precompute_mult(EC_GROUP *group, BN_CTX *ctx); int EC_GROUP_have_precompute_mult(const EC_GROUP *group); |
椭圆曲线参数
由曲线方程可知,要确定一条曲线,需要知道三个参数 a, b, p
。在 openssl 中用结构体 EC_GROUP
来表示。其结构定义在 ec_locl.h
中:
1 2 3 4 5 6 7 8 |
struct ec_group_st { // ... EC_POINT *generator; BIGNUM *field; BIGNUM *order, *cofactor; BIGNUM *a, *b; // ... }; |
generator
基点 Gfield
质数 pa, b
常量参数
一般来说,除非有特殊的需求会让你自定义一条曲线,否则直接使用 OPENSSL 内置的曲线,这些曲线是经过密码学专家或组织精心设计的:
1 |
EC_GROUP *EC_GROUP_new_by_curve_name(int nid); |
openssl 内置的曲线非常之多,可以使用 EC_get_builtin_curves
来获取:
1 2 3 4 5 6 7 8 9 10 11 12 |
void print_all_build_cures(){ int len = EC_get_builtin_curves(0,0); EC_builtin_curve* curves = (EC_builtin_curve*)malloc(sizeof(EC_builtin_curve) * len); len = EC_get_builtin_curves(curves, len); for(int i = 0; i<len; ++i){ std::cout<<curves[i].nid<<"\t"<<curves[i].comment<<std::endl; } free(curves); } |
这些曲线被定义在 ec_curve.c
中,如 NID_sm2
(也叫做 NID_sm2p256v1
) 的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
static const struct { EC_CURVE_DATA h; unsigned char data[0 + 32 * 6]; } _EC_sm2p256v1 = { { NID_X9_62_prime_field, 0, 32, 1 }, { /* no seed */ /* p */ 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* a */ 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, /* b */ 0x28, 0xe9, 0xfa, 0x9e, 0x9d, 0x9f, 0x5e, 0x34, 0x4d, 0x5a, 0x9e, 0x4b, 0xcf, 0x65, 0x09, 0xa7, 0xf3, 0x97, 0x89, 0xf5, 0x15, 0xab, 0x8f, 0x92, 0xdd, 0xbc, 0xbd, 0x41, 0x4d, 0x94, 0x0e, 0x93, /* x */ 0x32, 0xc4, 0xae, 0x2c, 0x1f, 0x19, 0x81, 0x19, 0x5f, 0x99, 0x04, 0x46, 0x6a, 0x39, 0xc9, 0x94, 0x8f, 0xe3, 0x0b, 0xbf, 0xf2, 0x66, 0x0b, 0xe1, 0x71, 0x5a, 0x45, 0x89, 0x33, 0x4c, 0x74, 0xc7, /* y */ 0xbc, 0x37, 0x36, 0xa2, 0xf4, 0xf6, 0x77, 0x9c, 0x59, 0xbd, 0xce, 0xe3, 0x6b, 0x69, 0x21, 0x53, 0xd0, 0xa9, 0x87, 0x7c, 0xc6, 0x2a, 0x47, 0x40, 0x02, 0xdf, 0x32, 0xe5, 0x21, 0x39, 0xf0, 0xa0, /* order */ 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x72, 0x03, 0xdf, 0x6b, 0x21, 0xc6, 0x05, 0x2b, 0x53, 0xbb, 0xf4, 0x09, 0x39, 0xd5, 0x41, 0x23, } }; |
二. 加解密
由 ECC 原理我们知道,椭圆曲线利用的是 在有限域上的椭圆曲线,已知曲线上的一个点 xG, 无法有效地求得 x
这样一个离散对数问题。 xG
可以做为公钥, x
做为私钥。
生成密钥对
API EC_KEY_generate_key(EC_KEY)
用于生成密钥对。 EC_KEY
的结构如下:
1 2 3 4 5 6 7 |
struct ec_key_st { // ... EC_GROUP *group; EC_POINT *pub_key; BIGNUM *priv_key; // ... }; |
包含一条曲线和一对密钥。当只有 pub_key
时,EC_KEY 做为公钥,当只有 priv_key
时,EC_KEY 做为私钥。
生成一对密钥的示例代码如下:
1 2 3 4 5 6 7 8 9 |
EC_KEY* key = EC_KEY_new(); EC_GROUP* group = EC_GROUP_new_by_curve_name(NID_X9_62_prime192v1); assert(group); int rst = EC_KEY_set_group(key, group); assert(rst == 1); rst == EC_KEY_generate_key(key); assert(rst); const EC_POINT* pub_key = EC_KEY_get0_public_key(key); const BIGNUM *pri_key = EC_KEY_get0_private_key(key); |
SM2 加解密
SM2 的 API 定义在 sm2.h
中。加解密用到的有:
1 2 |
int 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 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); |
加密用到的是公钥, 解密用到的是私钥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include <openssl/evp.h> #include <openssl/sm2.h> #include <openssl/sm3.h> const char *plain_text = "The EVP interface supports the ability to perform authenticated encryption and decryption, as well as the option to attach unencrypted," " associated data to the message. Such Authenticated-Encryption with Associated-Data (AEAD) schemes provide confidentiality by encrypting the data, and also" " provide authenticity assurances by creating a MAC tag over the encrypted data. The MAC tag will ensure the data is not accidentally altered or maliciously" " tampered during transmission and storage."; int main(void) { EC_POINT *pubkey = NULL; BIGNUM *prikey = NULL; GenECCKeys(pubkey, prikey); // 构建公钥私钥 EC_KEY *public_key = EC_KEY_new(); EC_KEY *private_key = EC_KEY_new(); EC_KEY_set_public_key(public_key, pubkey); EC_KEY_set_private_key(private_key, prikey); size_t cipher_text_len = 0, re_plain_text_len = 0; // 获取加密后的长度 cipher_text_len int rst = sm2_encrypt(public_key, EVP_sm3(), reinterpret_cast<const unsigned char *>(plain_text), strlen(plain_text), NULL, &cipher_text_len); unsigned char* cipher_text = (unsigned char*)malloc(cipher_text_len); unsigned char* re_plain_text = (unsigned char*)malloc(cipher_text_len); rst = sm2_encrypt(public_key, EVP_sm3(), reinterpret_cast<const unsigned char *>(plain_text), strlen(plain_text), cipher_text, &cipher_text_len); rst = sm2_decrypt(private_key, EVP_sm3(), cipher_text, cipher_text_len, re_plain_text, &re_plain_text_len); int cmp = memcmp(plain_text, reinterpret_cast<char*>(re_plain_text), re_plain_text_len); assert(cmp == 0); free(cipher_text); free(re_plain_text); EC_KEY_free(private_key); EC_KEY_free(public_key); return 0; } |
EC_KEY_set_group返回值为0是什么原因
请问GenECCKeys方法在哪里
在源码中是包含sm2目录的,但是在编译后就没有出现sm2头文件。我是用vs2019 利用指令 perl no-asm win
64A 编译的64位库,不过好像sm2那个文件没有编译?
@夜渡寒鸦
我看了下最新的源码,sm2.h 已经位于
https://github.com/openssl/openssl/blob/master/include/crypto/sm2.h
您好,请问在编译openssl 1.1.1d后,没有出现sm2.h、sm3.h,是编译有什么问题吗?博主方便交流一下吗?
请问
请问我在编译的时候出现 openssl/sm2.h:没有那个文件或目录 ,这是因为Openssl版本的原因吗?
文件位于
https://github.com/openssl/openssl/blob/master/crypto/include/internal/sm2.h
编译完成后,在openssl/include 目录下应该有该头文件你好,请问编译时出现 openssl/sm2.h:没有那个文件或目录 是因为Openssl版本的问题吗?