SM 国密算法踩坑指南
各位,好久不见~
最近接手网联的国密改造项目,由于对国密算法比较陌生,前期碰到了一系列国密算法加解密的问题。
所以这次总结一下,分享这个过程遇到的问题,希望帮到大家。
国密
什么是国密算法?
国密就是一个口头上简称,官方名称是国家商用密码,使用拼音缩写 SM,它是用于商用的、不涉及国家秘密的密码技术。
那说起密码技术,大家一定很熟悉 MD5,AES,RSA 等算法,这些都是通用国际标准算法。
而国密其实就是这些国际算法国产化的代替方案,与国际算法对应关系如下:
这次国密改造项目使用的就是 SM2 国密算法。
SM2算法
SM2 国密算法是一种非对称加密算法,基于 ECC(椭圆加密算法), SM2 算法对标我们常用的国际算法 RSA。
但是 SM2 算法由于基于 ECC,签名速度与秘钥速度都快于 RSA。另外 SM2 采用 ECC 256 位,安全强度比 RSA 2048 位更高,且运算速度同样也高于 ESA。
熟悉 RSA 算法同学应该知道,非对称加密算法,会有一对公私钥。
私钥可以用于加签,公钥可以用于验签。
公钥可以用于加密,私钥可以用于解密
同样 SM2 算法也有一对公私钥,它们的长度远远小于 RSA 公私钥。
SM2 私钥,一个大于等于 1 且小于 n-1的整数(n 为 sm2 算法的阶),长度为 256 位,即 32 个字节,通常会用 16 进制表示。
SM2 私钥:B17EACC0BB629AB92C591287F2FA4589D10CD1E13BD4BDFDC9589A940F937C7C
SM2 公钥,SM2 椭圆曲线上的一个点,由横坐标与纵坐标两个分量构成,每个长度分量长度为 256 位,通常也用 16 进制表示。
SM2 公钥一般有两种表示方法:
- X|Y,即 X与 Y两个分量拼接在一起,总共 64 个字节。
- 04|X|Y,有些给出公钥与上面格式一样,只不过前面增加 04,代表非压缩,整个公钥长度变成 65 字节。
- 分开展示,公钥 X,公钥 Y
公钥 X|Y:53B97D723AA4CEAC97A13B8C50AA53D40DE36960CFC3A3D7929FD54F39F824ED5A4A27AF871AD62C25C75C9D75C75A0907C565A78B805E9502E616C4E77F3B42
公钥 X:53B97D723AA4CEAC97A13B8C50AA53D40DE36960CFC3A3D7929FD54F39F824ED
公钥 Y:5A4A27AF871AD62C25C75C9D75C75A0907C565A78B805E9502E616C4E77F3B42
SM2 算法与 RSA 算法一样,可以用于数字签名,也可以用于加密场景,下面我们来看下数字签名场景下 SM2 算法原理。
SM2 数字签名算法
SM2 签名算法还是比较复杂,这里只截取数字签名的生成、验证算法原理。
详细文档可以搜索:『GB/T32918.2—2016 信息安全技术 SM2椭圆曲线公钥 密码算法 第2部分:数字签名算法』
sm2 加签
数字签名生成算法,即加签流程:
加签流程图如下:
sm2 验签
数字签名验证算法,即验签流程:
验签流程图:
SM2 签名数据
上面加签流程我们可以看到,SM2 加签之后产生的签名为(R,S),这一点与 RSA算法不同,RSA 算法加签之后签名就是一个值。
SM2 签名一般有两种数据格式,国标(GM/T 0009-2012 SM2 密码算法使用规范)规定签名数据格式,使用** ASN.1** 格式定义,具体格式如下:
通常使用硬件加密机加签产生的数字数字签名将会使用这种格式。
SM2 数字签名另外一种方式就比较简单,格式为R|S,即直接将两者拼接在一起表示。
通常使用软件加密产生数字签名将会使用这种数据格式。
SM2 公钥加密算法
SM2 加密算法也是比较复杂,这里只截取加密、解密原理
详细文档可以搜索:『GB/T 32918.4—2016 信息安全技术 SM2椭圆曲线公钥 密码算法 第4部分:公钥加密算法』
sm2 加密算法
SM2解密算法
SM2 加密数据
SM2 加密数据将会产生三个值:
C1 为随机产生的公钥
C2 为密文,与明文长度等长
C3 为 SM3 算法对明文数计算得到消息摘要,长度固定为 256 位
SM2 加密数据一般有两种数据格式,国标(GM/T 0009-2012 SM2 密码算法使用规范)规定加密数据格式,使用 ASN.1格式定义,具体格式如下:
通常使用硬件加密机加签产生的加密数据将会使用这种格式。
SM2 加密数据另外一种方式就比较简单,格式为 C1|C3|C2,即直接将三者拼接在一起表示。
通常使用软件加密产生数字签名将会使用这种数据格式。
这里需要注意一点,有些加密数据格式也会使用 C1|C2|C3,加解密之间需要注意格式。
SM2 相关问题
SM2 合规上通常需要使用硬件加密机,这种方案直接调用厂商的提供加密接口就好了,安全又比较简单。
但是这个方案需要采购相关硬件,成本比较高。
SM2 算法也可以使用软加密的方案,底层主要依赖 Bouncy Castle
库。
软加密的方案在于开箱即用,开发成本较低。
软件加密方案,Bouncy Castle
库封装的工具类,已经大大降低国密开发的难度。
如果不想开发可以直接使用 HuTool
工具类:
https://hutool.cn/docs/#/crypto/国密算法工具-SmUtil?id=介绍
如果想自己封装的话,可以参考下面文章
https://blog.csdn.net/pridas/article/details/86118774
https://github.com/xjfuuu/SM2_SM3_SM4Encrypt
不同的加密方案,加签、加密输出的结果格式不同。如果直接拿硬件加密方案生成加密结果,然后直接使用软件加密方案去解密,就会导致解密失败。
SM2 算法联调测试的时候,这一点比较头疼,下面讲下这次国密改造中碰到一些问题。
SM2 公私钥读取
SM2 如果用到数字签名,也用到加密的话,这个情况下我们就需要向 CA 机构,例如 CFCA,申请国密双证书。
CFCA 申请结果如下:
SM2 双证书,分为签名证书,加密证书。我们申请获取两个证书需要给到对手方,同样对手方也需要把他们双证书给我们。
这个过程签名需要使用自身签名证书对应的私钥,验签使用对手方签名证书包含的公钥。
加密使用对手方的加密证书包含的公钥,解密需要使用自身加密证书的对应的私钥。
这个流程比 RSA 单证书的情况复杂了很多。
我们拿到数字证书之后,如果需要从里面提取公钥,扩在下面的网站在线解析。
https://www.gmssl.cn/gmssl/index.jsp
下图选中就是证书中包含的公钥
SM2 数字签名问题
SM2 国标规定的加签数据格式使用 ASN.1,所以部分硬件厂商加签输出格式就是这种。
但是如果我们使用 BC 库加签输出格式直接使用 R|S。
如果是这种情况,我们就需要在明文 R|S 与 ASN.1 之间做相互的转换。
最新版本的 BC 库,已经提供转换的换方式。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.69</version>
</dependency>
可以使用下面的方式,输出签名结果为 ASN.1 格式
new SM2Signer(StandardDSAEncoding.INSTANCE, new SM3Digest());
也可以使用下面这种方式吗,输出签名结果为 R|S
// 前面输出 R|S
new SM2Signer(PlainDSAEncoding.INSTANCE, new SM3Digest());
如果是低版本,只能通过自己写代码转换了。代码就不贴了,参考下面这篇文章:
https://blog.csdn.net/pridas/article/details/86118774
SM2 加密问题
SM2 加密结果,国标规定使用 ASN.1 格式,所以部分硬件厂商加密结果使用这种格式。
但是 BC 库加密的结果是 C1|C3|C2,所以我们需要做一层转换。
转换代码如下:
将ASN1格式转成c1c3c2
/**
* 将ASN1格式转成c1c3c2
*
* @param asn1
* @return
* @throws IOException
*/
public static byte[] changeAsn1ToC1C3C2(byte[] asn1) throws IOException {
ASN1InputStream aIn = new ASN1InputStream(asn1);
ASN1Sequence seq = (ASN1Sequence) aIn.readObject();
BigInteger x = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
BigInteger y = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
byte[] c3 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
byte[] c2 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();
// 不压缩
ECPoint c1Point = GMNamedCurves.getByName("sm2p256v1").getCurve().createPoint(x, y);
byte[] c1 = c1Point.getEncoded(false);
return ArrayUtil.addAll(c1, c3, c2);
}
将 c1c3c2格式转成ASN1
private static final int C1_LEN = 65;
private static final int C3_LEN = 32;
/**
* 将c1c3c2转成标准的ASN1格式
*
* @param c1c3c2
* @return
* @throws IOException
*/
public static byte[] changeC1C3C2ToAsn1(byte[] c1c3c2) throws IOException {
byte[] c1 = Arrays.copyOfRange(c1c3c2, 0, C1_LEN);
byte[] c3 = Arrays.copyOfRange(c1c3c2, C1_LEN, C1_LEN + C3_LEN);
byte[] c2 = Arrays.copyOfRange(c1c3c2, C1_LEN + C3_LEN, c1c3c2.length);
byte[] c1X = Arrays.copyOfRange(c1, 1, 33);
byte[] c1Y = Arrays.copyOfRange(c1, 33, 65);
BigInteger r = new BigInteger(1, c1X);
BigInteger s = new BigInteger(1, c1Y);
ASN1Integer x = new ASN1Integer(r);
ASN1Integer y = new ASN1Integer(s);
DEROctetString derDig = new DEROctetString(c3);
DEROctetString derEnc = new DEROctetString(c2);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(x);
v.add(y);
v.add(derDig);
v.add(derEnc);
DERSequence seq = new DERSequence(v);
return seq.getEncoded(ASN1Encoding.DER);
}
这里需要注意一下,低版本的 BC 库加密结果是 C1|C2|C3,这就很坑了,现在很多都是 C1|C3|C2,这就又需要做转换。
转换代码参考这篇文章:
https://blog.csdn.net/pridas/article/details/86118774
总结
SM2 国密算法属于非对称加密算法,理解起来不是很难。
但是由于普及程度较低,现有资料太少,所以开发来还是比较复杂,碰到的问题也比较多。
建议大家开发之前可以先了解一下国密 SM2 相关国标规范,不需要很深入了解整个原理,但是需要知道国密 SM2 与 RSA 的区别点。
国密算法实现上,软加密我们可以直接用 BC 库,硬加密直接使用厂商提供的相关接口,这一点难度还好。
国密最大难度是,各个硬件与软加密,使用规范不一致,输出格式不一致,这就导致我们联调过程,加签/验签,加密/解密失败。
这就比较蛋疼,所以调试双方国密算法一致性过程中,建议先确认加签、加密输出格式,搞清楚这个,联调就比较简单了。
最后,祝大家对接国密算法顺利~
帮助资料
https://www.cnblogs.com/xinzhao/p/8963724.html
https://blog.csdn.net/pridas/article/details/86118774
https://github.com/xjfuuu/SM2_SM3_SM4Encrypt
SM 国密算法踩坑指南的更多相关文章
- SM系列国密算法(转)
原文地址:科普一下SM系列国密算法(从零开始学区块链 189) 众所周知,为了保障商用密码的安全性,国家商用密码管理办公室制定了一系列密码标准,包括SM1(SCB2).SM2.SM3.SM4.SM7. ...
- 20155206赵飞 基于《Arm试验箱的国密算法应用》课程设计个人报告
20155206赵飞 基于<Arm试验箱的国密算法应用>课程设计个人报告 课程设计中承担的任务 完成试验箱测试功能1,2,3 . 1:LED闪烁实验 一.实验目的 学习GPIO原理 ...
- 《基于Arm实验箱的国密算法应用》课程设计 结题报告
<基于Arm实验箱的国密算法应用>课程设计 结题报告 小组成员姓名:20155206赵飞 20155220吴思其 20155234昝昕明 指导教师:娄嘉鹏 设计方案 题目要求:基于Arm实 ...
- 2015520吴思其 基于《Arm试验箱的国密算法应用》课程设计个人报告
20155200吴思其 基于<Arm试验箱的国密算法应用>课程设计个人报告 课程设计中承担的任务 完成试验箱测试功能4,5,6以及SM3加密实验的实现 测试四 GPIO0按键中断实验 实验 ...
- C# -- HttpWebRequest 和 HttpWebResponse 的使用 C#编写扫雷游戏 使用IIS调试ASP.NET网站程序 WCF入门教程 ASP.Net Core开发(踩坑)指南 ASP.Net Core Razor+AdminLTE 小试牛刀 webservice创建、部署和调用 .net接收post请求并把数据转为字典格式
C# -- HttpWebRequest 和 HttpWebResponse 的使用 C# -- HttpWebRequest 和 HttpWebResponse 的使用 结合使用HttpWebReq ...
- Hyperledger Fabric密码模块系列之BCCSP(五) - 国密算法实现
Talk is cheap, show me your code. 代码也看了,蛋也扯了,之后总该做点什么.响应国家政策,把我们的国密算法融合进去吧-- 先附两张bccsp下国密算法的设计实现图. ...
- Bytom国密网说明和指南
比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 国密算法 ...
- 关于国密算法 SM1,SM2,SM3,SM4 的笔记
国密即国家密码局认定的国产密码算法.主要有SM1,SM2,SM3,SM4.密钥长度和分组长度均为128位. SM1 为对称加密.其加密强度与AES相当.该算法不公开,调用该算法时,需要通过加密芯片的接 ...
- Spring WebSocket踩坑指南
Spring WebSocket踩坑指南 本次公司项目中需要在后台与安卓App间建立一个长连接,这里采用了Spring的WebSocket,协议为Stomp. 关于Stomp协议这里就不多介绍了,网上 ...
随机推荐
- 基于live555开发嵌入式linux系统的rtsp直播服务
最近要搞一个直播服务,车机本身是个前后双路的Dvr,前路1080P 25fps,后路720P 50fps,现在要连接手机app预览实时画面,且支持前后摄像头画面切换. 如果要做直播,这个分辨率和帧率是 ...
- Luogu P1563 [NOIp2016提高组]玩具谜题 | 模拟
题目链接 纯模拟题,没啥好说的,就是要判断地方有点多,一定要注意细节. #include<iostream> #include<cstdio> #include<fstr ...
- 第01课 OpenGL窗口(4)
下面的代码处理所有的窗口消息.当我们注册好窗口类之后,程序跳转到这部分代码处理窗口消息. LRESULT CALLBACK WndProc( HWND hWnd, // 窗口的句柄 UINT uMsg ...
- 用C++实现的数独解题程序 SudokuSolver 2.7 及实例分析
引言:一个 bug 的发现 在 MobaXterm 上看到有内置的 Sudoku 游戏,于是拿 SudokuSolver 求解,随机出题,一上来是个 medium 级别的题: 073 000 060 ...
- glibc memcpy() 源码浅谈
其实我本来只是想搞懂为什么memcpy()函数的参数类型是void *的: 我以为会在memcpy()源码中能找到答案,其实并没有,void *只是在传递参数的时候起了作用,可以让memcpy()接受 ...
- List<String>转List<Integer>
List<Integer> intList = strList.stream().map(Integer::parseInt).collect(Collectors.toList()); ...
- Python打包成exe,文件太大问题解决办法
Python打包成exe,文件太大问题解决办法 原因 解决办法 具体步骤 情况一:初次打包 情况二:再次打包 原因 由于使用pyinstaller打包.py文件时,会把很多已安装的无关库同时打包进去, ...
- django的增删改查
前置条件: 已有一个model (tbl_user) ,用户表 1.查询 # 查询用户表 username是cx的数据 user_object = tbl_user.objects.filter(us ...
- install virtualenv without sudo
用普通用户安装virtualenv Perhaps this was valid for older versions of virtualenv. For now, if you want to r ...
- Intellij Idea显示回退和前进按钮的方法
方法1:使用快捷键: 回到上一步 ctrl + alt + <-(左方向键) 回到下一步 ctrl + alt + ->(右方向键) 方法2:在界面显示: View -> 勾选To ...