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);

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 2.5.4 - OpenSSL 1.1.0d 3 Sep 2019
/**
 使用gmssl SM2加密
 
 @param inData 需要加密的数据
 @param inDataLen 需要加密的数据长度
 @param pubKey 公钥(point2oct)
 @param pubKeyLen 公钥长度
 @param encryptData 加密后的数据
 @return 0:成功/非0:失败
 */
 int sm2EncryptWithGmssl(
                       unsigned char *inData,
                       unsigned long inDataLen,
                       unsigned char *pubKey,
                       unsigned long pubKeyLen,
                       SM2CiphertextValue **encryptData)
{
    int resultCode = Result_OK;
    //公钥
    EC_KEY *ec_key = NULL;
    //公钥
    EC_POINT *publicKey = NULL;
    //ec_group
    EC_GROUP *ec_group = NULL;
    //ctx
    BN_CTX *ctx = NULL;


    //判断输入参数是否为空
    if (inData == NULL || inDataLen == 0 || pubKey == NULL || pubKeyLen == 0 || encryptData == NULL) {
        resultCode = Result_InputErr;
        goto err;
    }
    
    //获取公钥
    ctx = BN_CTX_new();
    ec_group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
    publicKey = EC_POINT_new(ec_group);
    int mark = EC_POINT_oct2point(ec_group, publicKey, pubKey, pubKeyLen, ctx);
    if (mark != 1) {
        resultCode = Result_EncErr;
        goto err;
    }
    
    //初始化数据
    ec_key = EC_KEY_new_by_curve_name(NID_sm2p256v1);
    EC_KEY_set_public_key(ec_key, publicKey);
    
    //调用gmssl SM2加密
    if (!(*encryptData = SM2_do_encrypt(EVP_sm3(), inData, inDataLen, ec_key))) {
        resultCode = Result_EncErr;
        goto err;
    }
    
err:
    if (ec_key != NULL) {
        EC_KEY_free(ec_key);
    }
    if (ec_group != NULL) {
        EC_GROUP_free(ec_group);
    }
    if (ctx != NULL) {
        BN_CTX_free(ctx);
    }
    if (publicKey != NULL) {
        EC_POINT_free(publicKey);
    }
    return resultCode;
}

三、SM2解密算法

1、手动实现

  • 流程


    image.png
  • 算法:

1、从密文比特串C=C1||C3||C2中取出C1, 将C1转换成椭圆曲线上的点;

    #define POINT_BIN_LENGTH 65

    unsigned char c1Bin[POINT_BIN_LENGTH];
    unsigned long c1Binlen = POINT_BIN_LENGTH;
    memcpy(c1Bin, encrypt(密文), POINT_BIN_LENGTH);
    EC_POINT  *c1 = EC_POINT_new(ec_group);
    EC_POINT_oct2point(ec_group, c1, c1Bin, c1Binlen, ctx);

2、验证C1, 计算S=[h] C1,如果S是无穷远点,出错退出;

    int resultCode = EC_POINT_is_on_curve(ec_group, c1, ctx);
    if (resultCode) {
        printf("验证C1成功\n");
    }else{
        printf("验证C1失败\n");
    }

3、计算(x2,y2)=[dB] C1

    EC_POINT *dC1 = EC_POINT_new(ec_group);
    EC_POINT_mul(ec_group, dC1, NULL, c1, d, ctx);
    BIGNUM *x2 = BN_new();
    BIGNUM *y2 = BN_new();
    EC_POINT_get_affine_coordinates_GFp(ec_group,
                                        dC1, x2, y2, ctx);

4、计算t=KDF(x2||y2, klen), KDF是密钥派生函数,如果t是全0比特串,出错退出。

    unsigned char x2y2[64] = {0};
    unsigned long x2y2len = 0;
    //x2||y2
    x2y2len += BN_bn2bin(x2, x2y2);
    x2y2len += BN_bn2bin(y2, &x2y2[32]);
    //原文长度klen
    unsigned long klen = encryptLen - (c1Binlen+c3len);
    
    unsigned char t[klen];
    unsigned long tlen = klen;
    sm3_kdf1(EVP_sm3(), x2y2, sizeof(x2y2), t, &tlen);

5、从C=C1||C3||C2中取出C2,计算M’= C2+t。

    unsigned char c2[tlen];
    memcpy(c2, encrypt+c1Binlen+c3len, tlen);
    
    //原文
    unsigned char M[tlen+1];
    unsigned long Mlen = 0;
    for (int i = 0; i < tlen; i ++) {
        M[i] = c2[i] ^ t[i];
        Mlen++;
    }
    M[tlen] = '\0';
    printf("M'-->%s\n",M);

6、计算u=Hash(x2||M’||y2),比较u是否与C3相等,不相等则退出。
7、输出明文M’。

2、使用GMSSL库实现

  • 基于GmSSL 2.5.4 - OpenSSL 1.1.0d 3 Sep 2019
/**
 使用GMSSL解密
 
 @param cv 加密数据
 @param d 私钥
 @param decryptData 解密数据
 @param decryptDataLen 解密数据长度
 @return 0成功/其它失败
 */
  int sm2DecryptWithGmssl(SM2CiphertextValue *cv,BIGNUM *d,unsigned char *decryptData,unsigned long *decryptDataLen)
{
    int resultCode = 0;
    BN_CTX *ctx = NULL;
    
    EC_GROUP *ec_group = NULL;
    EC_KEY *ec_key = NULL;
    //bn_prime
    BIGNUM *prime = NULL;
    
    //判断输入参数是否为空
    if (cv == NULL || d == NULL || decryptData == NULL) {
        resultCode = Result_InputErr;
        goto end;
    }

    
    //初始化
    ctx = BN_CTX_new();
    ec_group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
    //设置私钥
    ec_key = EC_KEY_new();
    EC_KEY_set_group(ec_key, ec_group);
    EC_KEY_set_private_key(ec_key, d);
    //prime
    prime = BN_new();
    BN_hex2bn(&prime,SM2_n);
    
    //C = C1||C3||C2 -- C为加密数据encryptData
    if (!SM2_do_decrypt(EVP_sm3(), cv, decryptData, decryptDataLen, ec_key))
    {
        resultCode = Result_DecErr;
        goto end;
    }
    printf("\n Decrypt Data-->%s\n",decryptData);
end:
    if (ctx != NULL) {
        BN_CTX_free(ctx);
    }
    if (ec_group != NULL) {
        EC_GROUP_free(ec_group);
    }
    if (ec_key != NULL) {
        EC_KEY_free(ec_key);
    }
    if (prime != NULL) {
        BN_free(prime);
    }
    if (d != NULL) {
        BN_free(d);
    }
    return resultCode;
}

四、结论

  • 想要成功解密出原文,必须是公钥PB和私钥dB是匹配的,即满足PB=[dB]G,原文经过两次与同一比特串的异或计算,结果还是原文。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容