国密SM2算法系列操作
1. GMSSL安装
GMSSL安装步骤
装好之后可以调用命令行
2. GMSSL签发SM2密钥证书
GMSSL生成SM2证书
//生成私钥:
gmssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2p256v1 -pkeyopt ec_param_enc:named_curve -out skey.pem
//生成公钥
gmssl pkey -pubout -in skey.pem -out vrfykey.pem
公钥证书发给对方用于加密,私钥证书留存己方用于解密。
3. SM2概述
SM2是一种基于椭圆曲线密码算法的非对称加解密算法。
3.1椭圆曲线
方式一:自己指定椭圆曲线的参数。有限域上的一条椭圆曲线一共由六个量确定。有限域由两种(Fp和F2m,其中Fp为素数域)。六个量分别是:
p //素数域内点的个数
a //域内的一个大数
b //域内的一个大数,p、a、b确定一条椭圆曲线
x //基点的x坐标
y //基点的y坐标
n //基点的阶
有时,还会用到h(椭圆曲线上所有点的个数p与n相除的整数部分)。
根据这六个参数即可自己创建一个椭圆曲线
//详情参见GMSSL的sm2test.c
const char p_hex[64] = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF";
const char a_hex[64] = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC";
const char b_hex[64] = "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"
const char x_hex[64] = "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7";
const char y_hex[64] = "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0";
const char n_hex[64] = "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123";
EC_GROUP *group = NULL;
BN_CTX *ctx = NULL;
BIGNUM *p = NULL;
BIGNUM *a = NULL;
BIGNUM *b = NULL;
BIGNUM *x = NULL;
BIGNUM *y = NULL;
BIGNUM *n = NULL;
EC_POINT *G = NULL;
point_conversion_form_t form = SM2_DEFAULT_POINT_CONVERSION_FORM;
BN_hex2bn(&p, p_hex); //首先需要把hex转化为大数类型
...
BN_hex2bn(&n, n_hex);
group = EC_GROUP_new_curve_GFp(p, a, b, ctx); //根据pab生成素数域(阿贝尔群)
G = EC_POINT_new(group); //根据group生成基点
EC_POINT_set_affine_coordinates_GFp(group, G, x, y, ctx);//生成素数域上的仿射坐标
//若有限域为F2m,则为EC_POINT_set_affine_coordinates_GF2m(group, G, x, y, ctx);
EC_GROUP_set_asn1_flag(group, 0); //设置是否asn1编码,这关系到密文转换是o2i i2o还是i2d d2i
EC_GROUP_set_point_conversion_form(group, form);
return group;
方式二:采用SM2默认推荐的素数域256位的椭圆曲线
//初始化一个推荐椭圆曲线的算法组
EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
3.2 SM2密钥生成
SM2公私钥:P=[d]G,G是基点已知的,大数d为私钥,点P(xP, yP)为公钥,xP、yP分别为生成公钥所需的xy坐标。
方式一:自己指定参数。
//生成加密用的公钥
const char xP[64] = "435B39CCA8F3B508C1488AFC67BE491A0F7BA07E581A0E4849A5CF70628A7E0A";
const char yP[64] = "75DDBA78F15FEECB4C7895E21C1CDF5FE01DEBB2CDBADF45399CF77BBA076A42";
BIGNUM *x = NULL;
BIGNUM *y = NULL;
EC_KEY *ec_key = NULL;
ec_key = EC_KEY_new();
EC_KEY_set_group(ec_key, group); //指定密钥所用的算法组
BN_hex2bn(&x, xP); //将16进制数转化为大数
BN_hex2bn(&y, yP);
EC_KEY_set_public_key_affine_coordinates(ec_key, x, y); //设置公钥
//生成解密用的私钥
const char d_hex[64] = "1649AB77A00637BD5E2EFE283FBF353534AA7F7CB89463F208DDBC2920BB0DA0";
BIGNUM *d = NULL;
BN_hex2bn(&d,d_hex);
EC_KEY *ec_key = NULL;
ec_key = EC_KEY_new();
EC_KEY_set_group(ec_key, group); //指定密钥所用的算法组
EC_KEY_set_group(ec_key, d); //设置私钥
方式二:随机生成密钥对。
EC_KEY *keypair = NULL;
int ret1,ret2;
keypair = EC_KEY_new();
EC_GROUP *group1 = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
ret1 = EC_KEY_set_group(keypair, group1);
ret2 = EC_KEY_generate_key(keypair); //随机生成密钥对
方式三:从证书里读取公私钥。
//从证书文件中读取公钥(公钥为xy坐标确定的一个点)
char pub_pem_file_path[128] = "/tmp/vrfykey.pem";
BIO *pub_in = NULL;
pub_in = BIO_new(BIO_s_file()); //openssl输入输出流
if(BIO_read_filename(pub_in, pub_pem_file_path) <= 0){ //把pem文件信息读入文件流
return -1;
}
EC_KEY *pub_key = NULL;
PEM_read_bio_EC_PUBKEY(pub_in, &pub_key, NULL, NULL); //从文件流中读取公钥
//打印公钥
const EC_POINT *point_pub_key = EC_KEY_get0_public_key(pub_key); //从公钥中拿到点
const EC_GROUP *group = NULL;
group = EC_KEY_get0_group(pub_key); //从公钥中获取密钥生成时所用的算法组
BIGNUM *pub_x_bn = NULL;
BIGNUM *pub_y_bn = NULL;
BN_CTX *bn_ctx = NULL;
if(!(pub_x_bn = BN_new()) || !(pub_y_bn = BN_new()) || !(BN_CTX_new())){
return -1;
}
EC_POINT_get_affine_coordinates_GFp(group, point_pub_key, pub_x_bn, pub_y_bn, bn_ctx); //依据算法组从公钥点中拿坐标
char *pub_x = BN_bn2hex(pub_x_bn); //将公钥坐标由大数转化为十六进制
char *pub_y = BN_bn2hex(pub_y_bn);
printf("公钥的x坐标为:%s\n", pub_x);
printf("公钥的y坐标为:%s\n", pub_y);
//从证书文件中读取私钥
char *passin = NULL; //读取私钥证书所需的密码,无密码则为空
char pri _pem_file_path[128] = "/tmp/skey.pem";
BIO *pri_in = NULL;
pri_in = BIO_new(BIO_s_file()); //openssl输入输出流
if(BIO_read_filename(pri_in, pri_pem_file_path) <= 0){ //把pem文件信息读入文件流
return -1;
}
EC_KEY *pri_key = NULL;
PEM_read_bio_ECPrivateKey(pri_in, &pri_key, NULL, passin); //从文件流中读取私钥
//打印私钥
const BIGNUM *pri_key_bn = EC_KEY_get0_private_key(pri_key); //私钥转为大数
char *private_key = BN_bn2hex(pri_key_bn); //大数转为十六进制,方便打印
printf("私钥为:%s\n", private_key);
3.3 SM2加密
GM标准规定的SM2加密算法流程为:
旧标准的密文为C = C1 || C2 || C3,后来也有C = C1 || C3 || C2的形式,共两种。
实际运用中,GMSSL中已实现了该算法,函数名:SM2_do_encrypt(),我们只需要调用即可。
printf("/***********SM2加密测试***********/");
const char *plaintext = "test message";
const size_t plaintext_len = strlen(plaintext);
printf("明文为:%s\n", plaintext);
//进行加密
SM2Ciphertextvalue *cv = NULL; //标准格式的密文
const EVP_MD *md = EVP_sm3(); //SM2加密所用的摘要算法默认为SM3
if(!(cv = SM2_do_encrypt(md, (unsigned char *)plaintext, plaintext_len, pub_key))){ //杂凑函数、明文、明文长度、公钥
printf("SM2_do_encrypt fail.");
return -1;
}
//打印密文
const EC_GROUP *group = EC_KEY_get0_group(pub_key);
unsigned char cipher_buf[512] = {0};
unsigned char *cipher_text = cipher_buf;
int cipher_text_len = i2o_SM2CiphertextValue(group, cv, &cipher_text); //将标准密文cv转化为字符形式,存在cipher_buf中
//注意,若EC_GROUP_set_asn1_flag设置为1,则用i2d_SM2CiphertextValue(cv, &cipher_text)这个接口。
//EC_GROUP_new_by_curve_name(NID_sm2p256v1)出来的group默认asn1_flag为1.
printf("加密后的密文为:%s\n", cipher_buf);
printf("密文长度为:%d", cipher_text_len);
printf("密文的十六进制为:");
int i = 0;
for(i = 0; i < cipher_text_len; i++){
printf("%02x", cipher_buf[i]); //不足两位补齐,如6AB输出为06AB
}
printf("\n");
也可以往上调一层,调用sm2utl.c中的SM2_encrypt()函数。该函数本质上也是调用SM2_do_encrypt(),只是做了一层封装。调用如下:
在这里插入代码片
4.
------------------------------未完待续------------------------------