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协议这里就不多介绍了,网上 ...
随机推荐
- 大神教零基础入门如何快速高效的学习c语言开发
零基础如果更快更好的入门C语言,如何在枯燥的学习中找到属于自己的兴趣,如果把学习当成一种事务性的那以后的学习将会很难有更深入的进步,如果带着乐趣来完成学习那将越学越有意思这样才会让你有想要更深入学习的 ...
- 华为HCIP-Eth-trunk原理知识点
Eth-trunk(端口聚合.链路捆绑.链路聚合.以太通道) Eth-trunk技术出现的原因: • 随着网络中部署的业务量不断增长,对于全双工点对点链路,单条物理链路的带宽已不能满足正常的业务流量 ...
- [转]DDR3基础知识介绍
本文转自:(4条消息) xilinx ddr3 MIG ip核使用详解_admiraion123的博客-CSDN博客 1,DDR3基本内容介绍1.1,DDR3简介DDR3全称double-data-r ...
- 从零开始搭建你的nvim ide
前言概述 vim由于其丰富的扩展性.出色的跨平台性.高效率的操作性深受一大批粉丝的追捧,甚至就连vim和emacs之间孰优孰劣的话题都能被引起一场编辑器之间的圣战,足以见vim是多么的优秀. vim的 ...
- 设计模式(1-3)-动态代理(WeakCache的运用)
阅读本篇文章前,请事先阅读 理解Java的强引用.软引用.弱引用和虚引用. 看看什么是强引用.什么是弱引用及它们的用途,很必要!!! 上一节讲到,获取对应的代理类时,首先会从缓存中去拿,若拿不到才会去 ...
- 刷题日记-JZ25合并有序链表
合并有序链表 递归方式合并链表pHead1,pHead2 base case是 pHead1为空或者pHead2为空 递归方式是 如果pHead1->val < pHead2->va ...
- macos command 'clang' failed with exit status 1
export CC=$(which gcc)export CXX=$(which g++)pip install fbprophet CC=clang pip install gevent
- 史上最全的Excel导入导出之easyexcel
喝水不忘挖井人,感谢阿里巴巴项目组提供了easyexcel工具类,github地址:https://github.com/alibaba/easyexcel 文章目录 环境搭建 读取excel文件 小 ...
- c++学习笔记3(动态内存分配)
为了效率,需要按需去进行动态内存分配,在c中,可以用malloc去实现动态内存分配,而在c++中,则用new运算符去实现. 用法一:动态分配一个变量的存储空间 p=new T T为类型名 P则为T*类 ...
- Jetpack架构组件学习(1)——LifeCycle的使用
原文地址:Jetpack架构组件学习(1)--LifeCycle的使用 | Stars-One的杂货小窝 要看本系列其他文章,可访问此链接Jetpack架构学习 | Stars-One的杂货小窝 最近 ...