存储小咖 2018-12-28 18:38:11 4739 收藏 5
展开
GmSSL介绍
Gmssl介绍:http://gmssl.org/
当然本文也是参考 http://gmssl.org/
其中SM2为非对称算法
SM2密钥生成
pair<string, string> GenKey(void)
{
EC_KEY *keypair = NULL;
EC_GROUP *group1 = NULL;
keypair = EC_KEY_new();
if(!keypair) {
cout << "Failed to Gen Key" << endl;
exit(1);
}
group1 = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
if(group1 == NULL){
cout << "Failed to Gen Key" << endl;
exit(1);
}
int ret1 = EC_KEY_set_group(keypair, group1);
if(ret1 != 1){
cout << "Failed to Gen Key" << endl;
exit(1);
}
int ret2 = EC_KEY_generate_key(keypair);
if(ret2 != 1){
cout << "Failed to Gen Key" << endl;
exit(1);
}
size_t pri_len;
size_t pub_len;
char *pri_key = NULL;
char *pub_key = NULL;
BIO *pri = BIO_new(BIO_s_mem());
BIO *pub = BIO_new(BIO_s_mem());
PEM_write_bio_ECPrivateKey(pri, keypair, NULL, NULL, 0, NULL, NULL);
PEM_write_bio_EC_PUBKEY(pub, keypair);
pri_len = BIO_pending(pri);
pub_len = BIO_pending(pub);
pri_key = new char[pri_len + 1];
pub_key = new char[pub_len + 1];
BIO_read(pri, pri_key, pri_len);
BIO_read(pub, pub_key, pub_len);
pri_key[pri_len] = '\0';
pub_key[pub_len] = '\0';
string public_key = pub_key;
string private_key = pri_key;
EC_KEY_free(keypair);
BIO_free_all(pub);
BIO_free_all(pri);
delete [] pri_key;
delete [] pub_key;
return std::pair<string, string>(public_key, private_key);
}
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
已字符串形式的返回,主要考虑我们在实际运用中,可能需要传递密钥信息,所以生成的SM2公钥和私钥以字符串形式返回。
从字符串类型的密钥中生成出来EC_KEY对象
EC_KEY* CreateEC(unsigned char* key, int is_public)
{
EC_KEY *ec_key = NULL;
BIO *keybio = NULL;
keybio = BIO_new_mem_buf(key, -1);
if (keybio==NULL) {
cout << "Failed to Get Key" << endl;
exit(1);
}
if(is_public) {
ec_key = PEM_read_bio_EC_PUBKEY(keybio, NULL, NULL, NULL);
}
else {
ec_key = PEM_read_bio_ECPrivateKey(keybio, NULL, NULL, NULL);
}
if(ec_key == NULL) {
cout << "Failed to Get Key" << endl;
exit(1);
}
return ec_key;
}
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
SM2公钥加密
string Encrypt(const string& public_key, const string& plain_text)
{
unsigned char encrypted[1024] = {};
EC_KEY *rsa = CreateEC((unsigned char*)public_key.c_str(), 1);
size_t encrypted_length = 1024;
int ret = SM2_encrypt_with_recommended((unsigned char*)plain_text.c_str(), plain_text.length(),
(unsigned char*)encrypted,&encrypted_length, rsa);
if (ret == 0) {
cout << "Failed to Encrypt" << endl;
exit(1);
}
string enc_text((char*)encrypted, encrypted_length);
return enc_text;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这里使用的加密函数为SM2_encrypt_with_recommended
SM2私钥解密
string Decrypt(const string& private_key, const string& enc_text)
{
unsigned char decrypted[1024] = {};
EC_KEY * key1 = CreateEC((unsigned char*)private_key.c_str(), 0);
size_t decrypted_length = 0;
int ret = SM2_decrypt_with_recommended((unsigned char*)enc_text.c_str(), enc_text.length(), decrypted, &decrypted_length, key1);
if (ret == 0) {
cout << "Failed to Decrypt" << endl;
exit(1);
}
string plain_text((char*)decrypted, decrypted_length);
return plain_text;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这里使用的解密函数为SM2_decrypt_with_recommended
测试用例代码
下载:https://download.csdn.net/download/liulangaliulang/10884044
用例运行结果:
————————————————
版权声明:本文为CSDN博主「存储小咖」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liulangaliulang/article/details/85329878
SM2加解密算法(基于GMSSL的C代码实现)
一、椭圆曲线密码算法
-
椭圆曲线:是一类二元多项式方程,它的解构成一个椭圆曲线。
-
椭圆曲线参数:定义一条唯一的椭圆曲线。介绍其中两个参数G(基点)和n(阶)。G点(xG, yG)是椭圆曲线上的基点, 有限域椭圆曲线上所有其他的点都可以通过G点的倍乘运算计算得到,即P=[d]G, d也是属于有限域,d的最大值为素数n。
-
有限域上的椭圆曲线:椭圆曲线上的解不是连续的,而是离散的,解的值满足有限域的限制。有限域有两种,Fp和F2m。
-
E(Fq):Fq上椭圆曲线E 的所有有理点(包括无穷远点O)组成的集合。
-
Fp:一个素整数的集合,最大值为P-1,集合中的值都是素数,里面元素满足以下模运算: a+b=(a+b) mod p 和 ab=(ab) mod p。
-
SM2:有限域Fp上的一条椭圆曲线,其椭圆曲线参数是固定值。
-
公私钥:P=[d]G,G是已知的,大数d为私钥,点P(XP, YP)为公钥。
-
SM2推荐使用素数域256位椭圆曲线:
-->EC_GROUP_new_by_curve_name(NID_sm2p256v1)
//可以得出固定参数 //Sm2 中指定的参数 确定下y2 = x3 + ax + b 曲线 #define _P "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF" #define _a "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC" #define _b "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93" #define _n "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123" #define _Gx "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7" #define _Gy "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
- OpenSSL部分涉及代码
//初始化一个空算法组 EC_GROUP *group = EC_GROUP_new(EC_GFp_mont_method()); //初始化一个推荐椭圆曲线的算法组 EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_sm2p256v1); //上下文 BN_CTX *ctx = BN_CTX_new(); //创建EC_KEY,使用推荐椭圆曲线 EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_sm2p256v1) //生成公钥私钥 EC_KEY_generate_key(ec_key); //设置私钥 EC_KEY_set_private_key(ec_key, d); //设置公钥 EC_KEY_set_public_key(ec_key, P); //通过ec_key获取算法组 EC_GROUP *ec_group = EC_KEY_get0_group(ec_key); //获取基点G EC_POINT * G = EC_GROUP_get0_generator(ec_group); //大数初始化 BIGNUM *rand = BN_new(); //EC_POINT初始化 EC_POINT *P = EC_POINT_new(ec_group); //获取坐标点p的x,y值 EC_POINT_get_affine_coordinates_GFp(ec_group,p,x,y,ctx); //Gets the order of a EC_GROUP -- n阶 对应上面固定参数的_n EC_GROUP_get_order(ec_group, order, ctx); //随机数生成 do { BN_rand_range(rand,order); } while (BN_is_zero(rand)); //大数转二进制 int len = BN_bn2bin(bn, outChar); //获取坐标点p转大数bn EC_POINT_point2bn(ec_group, p, POINT_CONVERSION_COMPRESSED, bn, ctx); //点的乘积 lP = P * rand EC_POINT_mul(ec_group, lP, NULL, P, rand, ctx); //验证点C1是否在椭圆曲线上 EC_POINT_is_on_curve(ec_group, c1, ctx);
马上开始
二、SM2加密算法(手动实现和使用GMSSL库实现)
PS:加解密中,加密时椭圆曲线点C1转换方式必须和解密时椭圆曲线点C1转换方式一致,否则无法解出C1。
1、手动实现
-
流程
image.png -
算法:
1、产生随机数k, k的值从1到n-1;
BIGNUM *n,*k;
n = BN_new();
k = BN_new();
EC_GROUP_get_order(ec_group, n, ctx);
do {
BN_rand_range(k,n);
} while (BN_is_zero(k));
2、计算椭圆曲线点C1=[k]G=(x1,y1), 将C1使用EC_POINT_point2oct转换成比特串;
//获取基点G const EC_POINT *G = EC_GROUP_get0_generator(ec_group); EC_POINT *c1 = NULL; c1 = EC_POINT_new(ec_group); unsigned char c1bin[65]; unsigned long c1binlen = 65; EC_POINT_mul(ec_group, c1, NULL, G, k, ctx); EC_POINT_point2oct(ec_group, c1, POINT_CONVERSION_UNCOMPRESSED, c1bin, c1binlen, ctx);
3、 验证公钥PB, 计算S=[h] PB,如果S是无穷远点,出错退出;
EC_POINT_is_on_curve(ec_group, PB, ctx);
EC_POINT_is_at_infinity(ec_group, s);
4、计算(x2,y2)=[k] PB
EC_POINT *tempPoint = EC_POINT_new(ec_group); BIGNUM *x2 = BN_new(); BIGNUM *y2 = BN_new(); EC_POINT_mul(ec_group, tempPoint, NULL, pb, k, ctx); EC_POINT_get_affine_coordinates_GFp(ec_group, tempPoint, x2, y2, ctx);
5、计算t=KDF(x2||y2, klen), KDF是密钥派生函数,klen是明文长度。
unsigned char x2y2[64] = {0}; unsigned long x2y2len = 0; //x2||y2 x2y2len += BN_bn2bin(x2, x2y2); x2y2len += BN_bn2bin(y2, &x2y2[32]); unsigned char t[klen]; unsigned long tlen = klen; kdf(EVP_sm3(), x2y2, sizeof(x2y2), t, &tlen); //kdf方法 void *kdf(const EVP_MD *md, const void *in, size_t inlen, void *out, size_t *outlen) { EVP_MD_CTX ctx; uint32_t counter = 1; uint32_t counter_be; unsigned char dgst[EVP_MAX_MD_SIZE]; unsigned int dgstlen; unsigned char *pout = out; size_t rlen = *outlen; size_t len; EVP_MD_CTX_init(&ctx); while (rlen > 0) { counter_be = cpu_to_be32(counter); counter++; EVP_DigestInit(&ctx, md); EVP_DigestUpdate(&ctx, in, inlen); EVP_DigestUpdate(&ctx, &counter_be, sizeof(counter_be)); EVP_DigestFinal(&ctx, dgst, &dgstlen); len = dgstlen <= rlen ? dgstlen : rlen; memcpy(pout, dgst, len); rlen -= len; pout += len; } EVP_MD_CTX_cleanup(&ctx); return out; }
6、计算C2=M^t (此处^为异或)
unsigned char c2[tlen]; unsigned long c2len = 0; for (int i = 0; i < tlen; i ++) { c2[i] = M[i] ^ t[i]; c2len++; }
7、 计算C3=Hash(x2||M||y2)
unsigned char c3[32]; unsigned long c3len = 32; unsigned char tempC3[x2y2len+klen]; BN_bn2bin(x2, tempC3); BN_bn2bin(y2, &tempC3[32+klen]); memcpy(&tempC3[32], M, klen); sm3(tempC3, x2y2len+klen, c3);
8、 输出密文C=C1||C3||C2。
unsigned char c[c1binlen + c2len + c3len]; unsigned long clen = c1binlen + c2len + c3len; memcpy(c, c1bin, c1binlen); memcpy(&c[c1binlen], c3, c3len); memcpy(&c[c1binlen+c3len], c2, c2len);
注:密文分为C1,C2,C3,三部分,C1长度是65字节(具体根据转换方式),C2是明文的长度,C3是32字节(Hash使用sm3)。
注:C1 || C2 || C3 的意思就是拼在一起,而不是做什么或运算
- 根据国密推荐的SM2椭圆曲线公钥密码算法,首先产生随机数计算出曲线点C1,2个32byte的BIGNUM大数,即为SM2加密结果的第1部分(C1)。第2部分则是真正的密文,是对明文的加密结果,长度和明文一样(C2)。第3部分是杂凑值,用来效验数据(C3)。按国密推荐的256位椭圆曲线,明文加密结果比原长度会大97byte(C1使用EC_POINT_point2oct转换)。
- 注:通过密钥派生函数计算,才能进行第6步的按位异或计算。
2、使用GMSSL库实现
- 基于GmSSL 1.2.2 (OpenSSL 1.0.2d)
/** 修复BN_bn2bin函数结果不为32的情况(高位补0) @param sourceBn 源BIGNUM @param out 输出 @return 返回长度 */ int fixBn2Bin(BIGNUM *sourceBn,unsigned char *out){ unsigned char tempBin[32] = {0}; int tempLen = BN_bn2bin(sourceBn, tempBin); if (tempLen != 32) { memset(out, 0, 32 - tempLen); } memcpy(&out[32-tempLen], tempBin, tempLen); return 32; } #define RV_OK 0x00000000 //success #define RV_EncErr 0x01000010 //encrypt error /** SM2加密 使用国密GMSSL库 @param P 公钥 @param encryptData 需要加密的数据 @param outData 输出加密后的数据 @param outDataLen 输出加密后的数据长度 @return 0成功/其它失败 */ int sm2EncryptByGMSSL(EC_POINT *P,unsigned char *encryptData,unsigned long encryptDataLen,unsigned char *outData,unsigned long *outDataLen){ int resultCode = RV_OK; //变量 EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_sm2p256v1); EC_KEY_set_public_key(ec_key, P); const EC_GROUP *ec_group = EC_KEY_get0_group(ec_key); BN_CTX *ctx = BN_CTX_new(); SM2_CIPHERTEXT_VALUE *cv = NULL; SM2_ENC_PARAMS params; //c1 BIGNUM *x = NULL; BIGNUM *y = NULL; unsigned char *c1Buf = NULL; unsigned long c1Len = 0; //开始加密(使用GMSSL的方法) SM2_ENC_PARAMS_init_with_recommended(¶ms); if (!(cv = SM2_do_encrypt(¶ms, encryptData, encryptDataLen, ec_key))) { resultCode = RV_EncErr; goto end; } OPENSSL_assert(cv); //C1 //确定c1所需空间 if (!(c1Len = EC_POINT_point2oct(ec_group, cv->ephem_point, params.point_form, NULL, 0, ctx))) { resultCode = RV_EncErr; goto end; } //创建c1所需空间 c1Buf = malloc(c1Len); if (!(c1Len = EC_POINT_point2oct(ec_group, cv->ephem_point, params.point_form, c1Buf, c1Len, ctx))) { resultCode = RV_EncErr; goto end; } memcpy(outData, c1Buf, c1Len); //获取C1方法二 // unsigned char c1Bin[64] = {0}; // x = BN_new(); // y = BN_new(); // EC_POINT_get_affine_coordinates_GFp(ec_group,cv->ephem_point, x, y, ctx); // // fixBn2BinLen(x,c1Bin); // fixBn2BinLen(y,&c1Bin[32]); // //拼接C1 // memcpy(outData, c1Bin, 64); //后续拼接 //拼接C3 memcpy(outData