一、前言
关于国密算法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;
}
版权归原作者 xianmie 所有, 如有侵权,请联系我们删除。