0


SM2加解密代码及算法解析

一、前言

    关于国密算法SM2加解密的标准可参考国标文件:

http://c.gb688.cn/bzgk/gb/showGb?type=online&hcno=370AF152CB5CA4A377EB4D1B21DECAE0

下文中涉及到的符号约定也可参考国标文件以及我的上一篇分享:国密算法SM2 密钥对的生成_xianmie的博客-CSDN博客_sm2 秘钥生成

    想要更清晰明了地了解SM2算法,我的建议是要先了解一下椭圆曲线密码学的数学原理,大致搞明白:椭圆曲线、有限域(素域、二元域)、椭圆曲线的倍点运算。在此,推荐一篇博文,可帮助大家更好地理解ECC算法:ECC算法简析,椭圆曲线密码,应用于国密SM2_gentledongyanchao的博客-CSDN博客_ecc算法是国密算法吗

    上一篇博文中,已经介绍了SM2算法密钥对的生成,在此基础上,本文继续介绍SM2的加解密算法。

二、加解密算法流程

官方文档给出的加密算法流程:

解密算法流程:

  ![](https://img-blog.csdnimg.cn/e36615774e8647958b40f32a375cb529.png)

     为了更加清晰地说明SM2加解密算法到底做了什么,我将官方文档中的算法流程进行了总结,大家要是看文档看得不清楚,可以先看我这个简化版,再回过头看文档应该就会清晰很多。我这里**以一个加解密的通信过程为例**:

//密钥对生成

(1)用户A选定一条椭圆曲线Ep(a,b),并取椭圆曲线上一点,作为基点G。  

(2)用户A选择一个私钥****key(由随机数产生),并生成公钥****P=[key]G(倍点运算,公钥也是一个点)。 

(3)用户A将Ep(a,b)和点P,G传给用户B。

//加密

(4)用户B接到信息后 ,将待传输的明文****M进行摘要提取(使用SM3),产生一个随机数k,计算 t=KDF([k]P)。  

(5)用户B将 密文:C1=[k]G;** C2=M^t(异或运算)C3=****摘要 **传给用户A。

//解密

(6)用户A接到信息后,计算

    t’=KDF([key]C1)=KDF([key][k]G)=KDF([k]P)=t     由C2^t’=M^t^t’=M,即解出明文M

(7) 再将解出的明文进行摘要提取,验证和C3是否一致,若一致则说明解密成功

三、加密代码解释

大家可以对应上文的算法流程及代码的注释来理解

/**
  * @brief  SM2加密
  * @param  msg:    要加密的明文数据
  * @param  msglen: 明文数据长度
  * @param  wx:     公钥的x坐标
  * @param  wxlen:  公钥的x坐标长度,不超过32
  * @param  wy:     公钥的y坐标
  * @param  wylen:  公钥的y坐标长度,不超过32
  * @param  outmsg: 加密后密文 长度为明文 + 96
  * @retval -1:失败 msglen + 96:成功
  */
int sm2_encrypt(unsigned char *msg,int msglen, unsigned char *wx,int wxlen, \
                  unsigned char *wy,int wylen, unsigned char *outmsg)
{
 
    struct FPECC *cfig = &Ecc256;//椭圆曲线参数
    big x2, y2, c1, c2, k;
    big a,b,p,n,x,y;
    epoint *g, *w;
    int ret = -1;
    int i;
    unsigned char zl[32], zr[32];
    unsigned char *tmp;
    
    miracl instance;
    miracl *mip = &instance;

    tmp = malloc(msglen+64);//分配大小为明文长度+64的内存
    if(tmp == NULL)
        return -1;
    
    mip = mirsys(mip, 20, 0);   //初始化大数系统
    mip->IOBASE = 16;
    
    char mem[MR_BIG_RESERVE(11)];//定义数组以存放12个大数
    memset(mem, 0, MR_BIG_RESERVE(11));
    
    p= mirvar_mem(mip, mem, 0);//初始化大数变量
    a=mirvar_mem(mip, mem, 1);
    b=mirvar_mem(mip, mem, 2);
    n=mirvar_mem(mip, mem, 3);
    x=mirvar_mem(mip, mem, 4);
    y=mirvar_mem(mip, mem, 5);
    k=mirvar_mem(mip, mem, 6);
    x2=mirvar_mem(mip, mem, 7);
    y2=mirvar_mem(mip, mem, 8);
    c1=mirvar_mem(mip, mem, 9);
    c2=mirvar_mem(mip, mem, 10);
    
    cinstr(mip, p,cfig->p);//将字符串转换为整型
    cinstr(mip, a,cfig->a);
    cinstr(mip, b,cfig->b);
    cinstr(mip, n,cfig->n);
    cinstr(mip, x,cfig->x);
    cinstr(mip, y,cfig->y);
    
    ecurve_init(mip, a,b,p,MR_PROJECTIVE);//初始化椭圆曲线

    char mem1[MR_ECP_RESERVE(2)]; //定义数组以存放g、w两点
    memset(mem1 ,0, MR_ECP_RESERVE(2));

    g = epoint_init_mem(mip, mem1,0);//初始化椭圆上的点
    w = epoint_init_mem(mip, mem1,1);
     
    epoint_set(mip, x,y,0,g);//g=(x,y)为基点G,此x和y为结构体ECC256中的值
    bytes_to_big(mip, wxlen,(char *)wx,x);//将公钥值传入x和y
    bytes_to_big(mip, wylen,(char *)wy,y);
    epoint_set(mip, x,y,0,w);//点w=(x,y),此x和y为公钥的值
    
    //A1
    irand(mip, SEED_CONST);
sm2_encrypt_again:
    do
    {
        bigrand(mip, n, k);
    } while (k->len == 0);//生成随机大数k
    
    //A2:计算C1
    ecurve_mult(mip, k, g, g);//g=[k]g
    epoint_get(mip, g, c1, c2);//将g的坐标取出赋给c1,c2
    big_to_bytes(mip, 32, c1, (char *)outmsg, TRUE);//将g的x坐标转换为字符串作为加密后结果的[0:31]
    big_to_bytes(mip, 32, c2, (char *)outmsg+32, TRUE);//将g的y坐标转换为字符串作为加密后结果的[32:63]
    
    //A3:计算S=[h]PB;若S为无穷远点则报错并退出
    if(point_at_infinity(w))
        goto exit_sm2_encrypt;
    
    //A4:计算椭圆曲线点[k]PB
    ecurve_mult(mip, k, w, w);//w=[k]w
    epoint_get(mip, w, x2, y2);//x2=w(x),y2=w(y)
    big_to_bytes(mip, 32, x2, (char *)zl, TRUE);//将w的x、y坐标转换为字符串后赋给zl和zr
    big_to_bytes(mip, 32, y2, (char *)zr, TRUE);

    //A5:计算t = KDF,如果t全零,返回A1
    if (kdf(zl, zr, msglen, outmsg+64+32) == 0)
        goto sm2_encrypt_again;

    //A6:计算C2=M异或t,(t即outmsg[64+32:64+32+msglen])
    for(i = 0; i < msglen; i++)
    {
        outmsg[64+32+i] ^= msg[i];//此步后C2=outmsg[64:64+msglen]
    }
    //A7:计算C3
    memcpy(tmp, zl, 32);//tmp=x2||msg||y2
    memcpy(tmp+32, msg, msglen);
    memcpy(tmp+32+msglen, zr, 32);
    SM3Calc(tmp, 64+msglen, &outmsg[64]);//C3=Hash(tmp)
    //C=C1||C3||C2,即outmsg[0:63]=C1;outmsg[64:95]=C3;outmsg[96:96+msg]=C2
    ret = msglen+64+32;
    
exit_sm2_encrypt:

    memset(mem,0,MR_BIG_RESERVE(11));
    memset(mem1,0,MR_ECP_RESERVE(2));
    mirexit(mip);
    free(tmp);
    return ret;
}

四、解密代码解释

/**
* @brief  SM2解密
* @param  msg:        要解密的密文数据
* @param  msglen:     密文数据长度
* @param  privkey:    私钥
* @param  privkeylen: 私钥长度
* @param  outmsg: 解密后的明文 长度为明文 - 96
* @retval -1:失败 msglen - 96:成功
*/
int sm2_decrypt(unsigned char *msg,int msglen, unsigned char *privkey, \
                  int privkeylen, unsigned char *outmsg)
{
 
    struct FPECC *cfig = &Ecc256;
    big x2, y2, c, k;
    big a,b,p,n,x,y,key1;
    epoint *g;
    unsigned char c3[32];
    unsigned char zl[32], zr[32];
    int i, ret = -1;
    unsigned char *tmp;
    
    miracl instance;
    miracl *mip = &instance;
    
    if(msglen < 96)//长度<96则加密后的数据有问题,退出报错
        return 0;
    msglen -= 96;//减去96得到只含密文消息的数据长度
    tmp = malloc(msglen+64);
    if(tmp == NULL)
        return 0;
    
    mip = mirsys(mip, 20, 0);   
    mip->IOBASE = 16;
 
    char mem[MR_BIG_RESERVE(11)];
  memset(mem, 0, MR_BIG_RESERVE(11));
 
    x2 = mirvar_mem(mip, mem, 0);//初始化大数变量
    y2 = mirvar_mem(mip, mem, 1);
    c = mirvar_mem(mip, mem, 2);
    k = mirvar_mem(mip, mem, 3);
    p = mirvar_mem(mip, mem, 4);
    a = mirvar_mem(mip, mem, 5);
    b = mirvar_mem(mip, mem, 6);
    n = mirvar_mem(mip, mem, 7);
    x = mirvar_mem(mip, mem, 8);
    y = mirvar_mem(mip, mem, 9);
    key1 = mirvar_mem(mip, mem, 10);
    
    bytes_to_big(mip, privkeylen,(char *)privkey,key1);//将私钥转换为大数并赋给key1
    
    cinstr(mip, p,cfig->p);//将字符串转换为大数赋给p、a...y
    cinstr(mip, a,cfig->a);
    cinstr(mip, b,cfig->b);
    cinstr(mip, n,cfig->n);
    cinstr(mip, x,cfig->x);
    cinstr(mip, y,cfig->y);
    
    ecurve_init(mip, a,b,p,MR_PROJECTIVE);//初始化椭圆曲线

    char mem1[MR_ECP_RESERVE(1)]; 
    memset(mem1 ,0, MR_ECP_RESERVE(1));

    g = epoint_init_mem(mip, mem1,0);
    
    //B1:取出C1,验证C1是否满足椭圆曲线方程
    bytes_to_big(mip, 32, (char *)msg, x);//加密函数中的c1,c2赋给x,y
    bytes_to_big(mip, 32, (char *)msg+32, y);       
    if(!epoint_set(mip, x,y,0,g))//检验(c1,c2)是否在椭圆曲线上,若在g=C1
        goto exit_sm2_decrypt;         
    
    //B2:若S为无穷远点则报错并退出    
    if(point_at_infinity(g))//计算S:如果S=[h]C1在无穷远点则返回
        goto exit_sm2_decrypt;  
    
    //B3:计算[dB]C1=(x2,y2),并转换为字符串
    ecurve_mult(mip, key1, g, g);
    epoint_get(mip, g, x2, y2);    //x2=g(x),y2=g(y)
    big_to_bytes(mip, 32, x2, (char *)zl, TRUE);//转换为字符串,zl=x2,zr=y2
    big_to_bytes(mip, 32, y2, (char *)zr, TRUE); 
    
    //B4:计算t=KDF(x2||y2,klen),若t全为0则报错退出
    if (kdf(zl, zr, msglen, outmsg) == 0)
        goto exit_sm2_decrypt; 
    
    //B5:计算M到outsmg,M=C2异或t
    for(i = 0; i < msglen; i++)
    {
        outmsg[i] ^= msg[i+96];
    }   
    
    //B6:计算u=Hash(x2||M'||y2),若t!=C3则报错退出
    memcpy(tmp, zl, 32);//tmp=zl||outmsg||zr
    memcpy(tmp+32, outmsg, msglen);
    memcpy(tmp+32+msglen, zr, 32);
    
    SM3Calc(tmp, 64+msglen, c3);//计算u
    if(memcmp(c3, msg+64, 32) != 0)
    {
        goto exit_sm2_decrypt;
    }
    
    ret =  msglen;
exit_sm2_decrypt:
    memset(mem,0,MR_BIG_RESERVE(11));
    memset(mem1,0,MR_ECP_RESERVE(1));
    mirexit(mip);
    free(tmp);
    return ret;
}
标签: 算法 网络安全

本文转载自: https://blog.csdn.net/xianmie/article/details/128253933
版权归原作者 xianmie 所有, 如有侵权,请联系我们删除。

“SM2加解密代码及算法解析”的评论:

还没有评论