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的加解密算法。

二、加解密算法流程

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

解密算法流程:

      

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

//密钥对生成

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

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

(3)用户AEp(a,b)和点PG传给用户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;
}

 

 

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些关于 JavaScript 和 Java 中 SM2 加解密的基本信息。 JavaScript 中的 SM2 加解密可以使用 jsrsasign 库进行实现。以下是一个简单的示例代码: ```javascript // 密钥对生成 var ec = new KJUR.crypto.ECDSA({'curve': 'sm2'}); var keypair = ec.generateKeyPairHex(); // 加密 var pubKey = keypair.ecpubhex; var plainText = 'hello world'; var cipher = KJUR.crypto.Cipher.encrypt(pubKey, plainText, 'SM2'); // 解密 var privKey = keypair.ecprvhex; var decrypted = KJUR.crypto.Cipher.decrypt(privKey, cipher, 'SM2'); console.log(decrypted); ``` Java 中的 SM2 加解密可以使用 Bouncy Castle 库进行实现。以下是一个简单的示例代码: ```java // 密钥对生成 ECNamedCurveParameterSpec sm2Spec = ECNamedCurveTable.getParameterSpec("sm2p256v1"); KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider()); kpg.initialize(sm2Spec, new SecureRandom()); KeyPair keyPair = kpg.generateKeyPair(); // 加密 Cipher cipher = Cipher.getInstance("SM2", new BouncyCastleProvider()); cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic()); byte[] plainText = "hello world".getBytes(); byte[] cipherText = cipher.doFinal(plainText); // 解密 cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate()); byte[] decrypted = cipher.doFinal(cipherText); System.out.println(new String(decrypted)); ``` 需要注意的是,JavaScript 和 Java 中的 SM2 加解密算法的实现细节可能有所不同,需要根据具体需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值