修订记录
版本 | 修订人 | 修订内容 | 修订日期 |
---|---|---|---|
V1.0 | 王旭 | 创建文档 | 2023-11-10 |
国密(国家密码算法)是指中国国家密码管理局发布的一系列密码标准和算法,用于保护信息安全、数据加密、数字签名和电子认证。这些算法和标准被广泛应用于中国政府、军队、金融、电信、互联网等行业。
一、相关定义
在椭圆曲线密码学(Elliptic Curve Cryptography,ECC)中,椭圆曲线的方程和相关的密码学参数,参数的含义如下:
-
SM2: 国密算法的一部分,用于椭圆曲线公钥密码学。SM2是一种数字签名、密钥交换和公钥密码算法,用于加密和签名数据。
-
SM3: 一种国密密码杂凑算法,类似于SHA-256。它用于计算数据的哈希值,通常用于数字签名和数据完整性验证。
-
SM4: 一种分组密码算法,类似于AES(高级加密标准)。它用于数据加密和解密,包括对称密钥加密。
-
X.509: 一个用于公钥基础设施(PKI)的标准,包括数字证书格式、证书颁发机构(CA)和数字签名等方面的规范。
-
Certificate(数字证书): 一种包含公钥和相关身份信息的数据结构,通常用于身份验证和数字签名。
-
椭圆曲线: 一种数学结构,被国密算法中的SM2和SM9所广泛使用。椭圆曲线密码学提供了高度安全的公钥加密和数字签名。
-
公钥和私钥: 公钥用于加密和验证数字签名,私钥用于解密和生成数字签名。在国密算法中,公钥和私钥的长度通常取决于特定的算法和参数。
-
DHex和QHex: 在椭圆曲线密码学中,DHex是私钥(整数),而QHex是公钥(坐标对)。它们通常以十六进制表示。
-
ASN编码和DER编码: ASN.1(抽象语法标记一)是一种用于描述数据结构的标准,而DER(Distinguished Encoding Rules)是一种将ASN.1数据编码成二进制的规则。在X.509证书和其他加密标准中,ASN编码和DER编码用于表示和交换数据。
-
p (Prime):这是一个素数,表示在有限域上的椭圆曲线的模。椭圆曲线的点坐标的值是模 p 意义下的整数。它决定了椭圆曲线的位数,即曲线上点的个数。
-
a:这是曲线方程中的系数,通常叫做 a,它用于定义椭圆曲线的形状。在SM2中,a 的值是一个特定的整数。
-
b:这也是曲线方程中的系数,通常叫做 b,它用于定义椭圆曲线的形状。在SM2中,b 的值是一个特定的整数。
-
n (Order):n 是椭圆曲线上的基点 G 重复相加后达到无穷远点的次数。它通常是一个素数,也称为曲线的阶。它决定了椭圆曲线上点的周期性。
-
xG, yG (Generator Point):这是椭圆曲线上的一个基点,通常用 (xG, yG) 表示。基点是曲线上的一个已知点,用于生成其他点。在SM2中,这是一个特定的点,它与曲线的 p 参数一起定义了曲线。
-
C1(压缩的非零值): C1是一个点,表示加密时生成的临时公钥。这个点是一个曲线上的坐标,通常包括X和Y两个分量。由于X坐标的压缩,C1的长度是固定的。
-
C2(密文数据): C2是加密后的实际数据部分,即原始明文使用临时公钥加密得到的密文。
-
C3(辅助信息): C3包含了对C1和C2的Hash值,用于确保消息的完整性和真实性。
PKCS 是 “Public Key Cryptography Standards” 的缩写,是一组密码学标准和规范,用于在信息安全领域进行公钥密码学的各种操作。以下是 PKCS 标准的一些常见类型:
-
PKCS #1: 该标准定义了一组密码学方法,包括 RSA 加密、RSA 数字签名、RSA 密钥交换和其他相关操作。PKCS #1 规定了 RSA 密钥的格式和加密和签名的标准。
-
PKCS #7: 该标准定义了 Cryptographic Message Syntax (CMS),一种用于创建数字签名和加密消息的通用语法。PKCS #7 主要用于创建数字证书、数字签名、数据加密和数据压缩的消息格式。
-
PKCS #8: 该标准定义了一种通用的私钥信息语法,通常用于表示和交换非对称密码算法的私钥信息。PKCS #8 规定了私钥的编码格式。
-
PKCS #12: 也称为 PFX 格式,它定义了一种通用的文件格式,用于存储和传输包含私钥、公钥、证书和其他相关数据的安全项目。PKCS #12 标准通常用于将数字证书和相关密钥存储到单个文件中,以便于安全备份和传输。
-
这些参数一起定义了椭圆曲线,而SM2曲线的参数是根据国际标准 GB/T 32918.5-2017 规定的。这些参数在SM2算法中用于密钥生成、加密和签名等操作,确保了SM2算法的安全性和可靠性
-
P224Curve (P-224):使用224比特的椭圆曲线。这意味着曲线上的点的坐标使用224比特表示。P-224曲线的方程和参数由NIST定义。
-
P256Curve (P-256):使用256比特的椭圆曲线,同样是由NIST定义。P-256是NIST曲线中最常用的一个,也是许多密码学协议和标准中使用的默认曲线。
-
P521Curve (P-521):使用521比特的椭圆曲线,同样由NIST定义。P-521提供了更大的安全性,但计算成本也更高。
-
SM2P256Curve (SM2 P-256):SM2标准定义的椭圆曲线,也使用256比特。与NIST的P-256相比,SM2曲线的方程和参数不同,它是中国国家密码管理局(CNCA)定义的国密算法中使用的曲线。
ASN.1(Abstract Syntax Notation One)是一种用于描述数据结构的标记语言,而 BER(Basic Encoding Rules)是 ASN.1 数据结构的一种二进制编码规则。ASN.1 不仅用于描述数据结构,还定义了多种不同的编码规则,其中 BER 是其中之一。下面是一些与编码方式相关的概念:
-
BER(Basic Encoding Rules): BER 是 ASN.1 数据结构的一种基本二进制编码规则。它定义了如何将 ASN.1 数据结构编码为二进制数据,以便在计算机系统之间传输和存储。BER 编码是一种自描述的编码方式,包含了标签、长度和值。这使得数据可以在接收者端被解码和还原。
-
DER(Distinguished Encoding Rules): DER 是 BER 编码的子集,它增加了更多的规则以确保编码的唯一性。DER 编码规则通常用于数字证书(如 X.509 证书)以及其他需要数据完整性和唯一性的应用。DER 编码要求对于相同的数据结构,生成的编码结果始终相同。
-
ASN.1 编码: ASN.1 是一种数据结构描述语言,它定义了如何描述数据的类型和结构。ASN.1 定义了数据的语法和语义。ASN.1 编码是一种通用的编码方式,用于将数据结构编码为二进制形式,以便在不同系统之间传输和存储。ASN.1 编码可以使用多种规则,包括 BER 和 DER。
-
X.690 标准: X.690 是 ITU-T 标准中关于 ASN.1 数据结构编码的规范,它包括 BER 和 DER 编码规则的详细规范。这些规范定义了如何对数据结构进行编码和解码,以确保数据的一致性和可互操作性。
总结一下,ASN.1 是一种用于描述数据结构的语言,而 BER 和 DER 是 ASN.1 数据结构的具体二进制编码规则。ASN.1 编码可以使用多种规则,其中 BER 和 DER 是最常用的。BER 是一种基本编码规则,而 DER 是增强版的 BER,用于确保数据的唯一性。 X.690 标准规定了这些编码规则的详细规范。
二、原理
ECC模型
椭圆曲线可以简单的理解为公式(Weierstrass 方程):
这是一个数学公式,它的理论基础要从平行线谈起。数学上人们认为的平行线永不相交,这在某种程度上是无法验证的,因为没有一个无限远处的概念,假设平行线在无限远处相交,这样的好处是所有的直接都有且只有一个焦点,那么基于这个事实,我们中学学过的笛卡尔平面直角坐标系可以映射出另外一个平面坐标系:
假设平面直角坐标系中有点A(x,y),我们定义 X= x/z,Y = y/z,Z=z;那么联立方程:aX+bY+c1Z =0; aX+bY+c2Z =0 可以计算出z=0;所以我们新的坐标系中的点可以表示为:(X:Y:0);
下面在看椭圆曲线的定义:
数学把满足Weierstrass方程的曲线称为椭圆曲线
这个方程的曲线生成的图像有很多,这里介绍形如:y2=x3+ax+b的公式,因为这种情况下的椭圆曲线才适合加密,例如y2=x3-10x+12的曲线如下:
感兴趣的可以去https://www.desmos.com/calculator/0mnue7w8lk 模拟一下,通过修改a,b的值观看曲线变化。需要注意的是并不是所有椭圆曲线都是关于X轴对称的。可以改变多改变a1的值来发现。
数学家在这个曲线上定义了一种椭圆曲线的加法,在上面定义的公式曲线图中:
显然这并不是传统的数学上的加法,运算法则:任意取椭圆曲线上两点P、Q (若P、Q两点重合,则做P点的切线)做直线交于椭圆曲线的另一点R,过R做y轴的平行线交于R’。我们规定P+Q=R’。所以很容易理解nP的值,就是P经过n次加法(对P做切线,取得另一个交点的关于X轴的对称点)。
ECC算法
ECC椭圆曲线由很多点组成,这些点由特定的方程式组成的,比如方程式可以是y^2 = x^3 + ax + b,这些点连接起来就是一条曲线,但曲线并不是一个椭圆。
椭圆曲线有个特点,任意两个点能够得到这条椭圆曲线上的另外一点,这个操作称为打点,经过多次(比如d次)打点后,能够生成一个最终点(F)。
在上面的图中,A点称为基点(G)或者生成器。A可以和自己打点从而生成B点,在实际应用的时候,一般有基点就可以了。经过多次打点,就得到了最终点G。
ECC密码学的关键点就在于就算知道具体方程式、基点(G)、最终点(F),也无法知晓一共打点了多少次(d)。
ECC中,打点次数(d)就是私钥,这通常是一个随机数,公钥就是最终点(F),包含(x,y)两个分量,通常组合成一个数字来传输和存储。
ECC由方程式(比如a、b这样的方程式参数)、基点(G)、质数(P)组成。理论上方程式和各种参数组合可以是任意的,但是在密码学中,为了安全,系统预先定义了一系列的曲线,称为命名曲线(name curve),比如secp256k1就是一个命名曲线。对于开发者而言,在使用ECC密码学的时候,就是选择具体的命名曲线。
SM2算法是ECC算法的一种,相当于是设计了一条ECC命名曲线。
##ECC算法的形象比喻
在密封的房间内踢一小时球,一直球的起点和终点,很难得出球踢过几次。
非对称加密算法中的加密解密和加签验签
参考文档:
SSH 公钥 私钥的理解
SSH,公钥,私钥的理解
公钥、私钥的结构说明(使用golang说明)
1.公钥结构体:
- X 坐标(XCoordinate):一个大整数,表示椭圆曲线上的点的 X 坐标。
- Y 坐标(YCoordinate):一个大整数,表示椭圆曲线上的点的 Y 坐标。
- Curve表示使用的椭圆曲线类型,如:SM2P256Curve (SM2 P-256):SM2标准定义的椭圆曲线,使用256比特。
type PublicKey struct {
elliptic.Curve
X, Y *big.Int
}
2.私钥结构体:
PublicKey,表示 SM2 密钥对的公钥部分。D表示 SM2 密钥对的私钥部分。 SM2 的私钥是一个大整数 (*big.Int),而公钥部分嵌套了在 sm2.PublicKey 结构中。
type PrivateKey struct {
PublicKey
D *big.Int
}
3.实际使用的公钥(64字节)
在项目中使用的公钥一般是由结构体中的x坐标和y坐标做拼接成的,如:
- x轴坐标的hexString为:01DE3FD801F7EE11F3E21DA31FBB11DF648E802FC875FB9D1F6EC1BD0E62FA3D
- y轴坐标的hexString为:269DA82CB6CDD558CA26BB3C85369EED85FCE9A6C2949A2B303D321858DED2EC
- 则未压缩的publicKeyStr(qHexString)为:01DE3FD801F7EE11F3E21DA31FBB11DF648E802FC875FB9D1F6EC1BD0E62FA3D269DA82CB6CDD558CA26BB3C85369EED85FCE9A6C2949A2B303D321858DED2EC
因为椭圆方程已知,则压缩公钥只需要x轴的坐标,带入方程后即可得到y轴坐标, - 则压缩publicKeyStr(qHexString)为:01DE3FD801F7EE11F3E21DA31FBB11DF648E802FC875FB9D1F6EC1BD0E62FA3D
4.实际使用的私钥(32字节) :
- 在项目中使用的公钥一般是一个bigint转码成的16进制字符串:
5ce72ea2503218de7f32e5005a10d2f57355253f664dc34128b84b2193b03da0
5.实际使用中的随机数(32字节):
随机数是为了安全。在加密时有用,解密时用不到。加密时之所以随时数为非必填,代码里给你指定了固定的一个值。随时数不同,加密后的内容是不同的。但是,解密时,只要私钥pB正确,都能正确的解密出明文,这厉害吧。
ps:SM2公钥是64 byte,如果收到的是65 byte的公钥数据,第1个byte是填充数据0x04。根据实际需求删除这个填充字节。同样的,私钥数据有可能第一个字节是填充数据0x00
6.工具中的使用示例
##国密sm2算法中公钥的生成
公钥(Q)是由椭圆曲线上的点(X, Y)组成的。
需要注意的是,公钥(Q)的生成是可逆的,也就是说,知道私钥(D)和基础点(P)的情况下,可以重新生成相同的公钥(Q)。然而,从公钥(Q)生成私钥(D)是一个非常困难的问题,这就是非对称加密的核心原理,只有私钥的持有者才能够执行这个操作。
公钥(Q)的生成通常是通过椭圆曲线上的点乘法(Point Multiplication)来完成的,其中私钥(D)是一个整数,它乘以椭圆曲线上的一个点P,得到公钥(Q)。
具体生成步骤如下:
-
选择一个椭圆曲线,通常使用一种标准的椭圆曲线,如SM2算法使用的椭圆曲线。这个椭圆曲线定义了曲线上的运算规则。
-
选择一个基础点(Base Point)P,通常也是椭圆曲线上的一个事先定义好的点。
-
生成私钥(D),私钥是一个随机的整数,通常在一定的范围内生成。
-
通过点乘法,将私钥(D)与基础点(P)相乘,得到公钥(Q)。这个操作在椭圆曲线上进行,其实质是将点P与自身相加(点的自加运算),然后重复这个操作D次。
##签名、密文的结构说明
1.密文结构:
type SM2Ciphertext struct {
C1 []byte
C2 []byte
C3 []byte
}
或
c1 := ciphertext[0:64]
c3 := ciphertext[64:96]
c2 := ciphertext[96:]
result := &sm2Cipher{
XCoordinate: new(big.Int).SetBytes(c1[:32]),
YCoordinate: new(big.Int).SetBytes(c1[32:]),
HASH: c3,
CipherText: c2,
}
SM2加密结果有两种数据组织方式,最常见的是C1|C3|C2(最新标准格式),另一种C1|C2|C3,具体解释:
-
首先生成C1(随机生成SM2椭圆曲线上的一个点),2个32byte的BigInteger大数,即为SM2加密结果的第1部分,固定64B。
-
第2部分C2则是真正的密文,是对明文的加密结果,长度和明文一样(如 明文为"aaaa",则密文的长度为4字节,若明文为:“我爱你”,因为一个中文字符占两个字节,密文长度为6字节)。
-
第3部分C3是杂凑值(HASH),用来效验数据,固定32B。
SM2算法加密密文长度=明文长度+96B;
2.签名结构:
type sm2Signature struct {
R, S *big.Int
}
在SM2算法中,数字签名使用了椭圆曲线数字签名算法(ECDSA),其中 r 和 s 是两个大整数,用于表示签名的一部分。具体来说:
-
r 是签名的一部分,表示椭圆曲线上的点 (x, y) 中的 x 坐标的值。
-
s 是签名的另一部分,是通过对消息的哈希值、私钥和 r 的计算得到的。
ps:
- C1有可能是65 byte,最前面填充0x00 表示正数。
- BC加密库生成的SM2密文,默认会在前面加0x04。如果解密端不是使用BC库,就要注意先删除这个字节再解密。
曲线选取
gmsm库中提供了多种曲线可以选取,224bit、256bit、521bit中,256bit是最常用的,兼具加解密是速度和安全性,一般会选取P256曲线。
##asn1编码
加密/加签过程中,某些语言/某些库会默认加密/加签为asn1编码格式,故多语言对接时,需要考虑解码/反序列化,在下面的代码示例中会有提到。
以下是一个简单的ASN.1编码示例,用于表示一个包含姓名和年龄的人的信息:
PersonInfo ::= SEQUENCE {
name UTF8String,
age INTEGER
}
personData PersonInfo ::= {
name "John Doe",
age 30
}
在上面的示例中,我们定义了一个名为 PersonInfo 的SEQUENCE类型,它包含两个字段:name 和 age。然后,我们使用 personData 将实际数据填充到这个数据结构中。
对应的ASN.1编码结果可能如下所示(以DER编码为例,实际编码可能会有轻微差异):
30 15 0C 09 4A 6F 68 6E 20 44 6F 65 02 03 03 12
这个编码表示:
- 30:表示SEQUENCE类型的标记。
- 15:表示后续数据的长度。
- 0C 09:表示UTF8String类型的标记,后面的长度为9。
- 4A 6F 68 6E 20 44 6F 65:UTF8String类型的值 “John Doe”。
- 02 03:表示INTEGER类型的标记,后面的长度为3。
- 03 12:INTEGER类型的值 30。
这是一个简单的ASN.1编码示例,实际的ASN.1编码可能更复杂,特别是在处理复杂的数据结构和更多数据类型时。
ASN.1 编码规则https://www.jianshu.com/p/fccd30a76505
深入理解规范
国家密码管理局关于发布《SM2椭圆曲线公钥密码算法》公告:附件1、附件2
三、sm2加密、解密、加签、验签代码示例
java加密、加签,go解密、验签
java端使用asn1编码:
public static void main(String[] args) {
//需要加密的明文
String text = "aaaa";
//创建sm2 对象
SM2 sm2 = SmUtil.sm2();
//这里需要手动设置,sm2对象的默认值与我们期望的不一致
//这里会自动生成对应的随机秘钥对 , 注意! 这里一定要强转,才能得到对应有效的秘钥信息
byte[] privateKey = BCUtil.encodeECPrivateKey(sm2.getPrivateKey());
//这里公钥不压缩 公钥的第一个字节用于表示是否压缩 可以不要
byte[] publicKey = ((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false);
//设置加密时,密文的结构是c1c3c2
sm2.setMode(SM2Engine.Mode.C1C3C2);
//设置使用标准格式编码,即使用asn1编码方式 如果使用PlainDSAEncoding,密文/签名直接转为字节数组
//asn1编码的签名十六进制字符串开头一般为30(规律总结,未找到相关资料)
sm2.setEncoding(new StandardDSAEncoding());
//使用sm3生成摘要
sm2.setDigest(new SM3Digest());
//打印当前的公私秘钥
System.out.println("私钥: " + HexUtil.encodeHexStr(privateKey));
System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey));
//得到明文对应的字节数组
byte[] dateBytes = text.getBytes();
String encryptHex = sm2.encryptHex(dateBytes, KeyType.PublicKey);
System.out.println("加密数据:"+encryptHex);
String decryptStr = sm2.decryptStr(encryptHex, KeyType.PrivateKey);
System.out.println("解密数据:"+decryptStr);
//计算签名
byte[] sign = sm2.sign(dateBytes, null);
System.out.println("签名: " + HexUtil.encodeHexStr(sign));
// 校验 验签
boolean verify = sm2.verify(dateBytes, sign);
System.out.println(verify);
}
func main() {
//原文
text := "aaaa"
//私钥
dHex := "009f64da25dee0f2b3ed816b3c7b102d7e26c3b0c6915956f5f215445b74ff5b70"
//公钥
qHex := "048c73987374bb420811e9deee14a134e70c32fb89d41254d2951a8b7c8945fbaf96f432aae078788684b6ffe9fadfe97d8651d27ad2f047ad3f80254a10ce1cd5"
//密文
encryptHex := "04072f67a4a98e8d7aad5b762e0be86e9076dbf68670e98f7519713d5240406b5601bc19e95bef6bb2f5c2dbbd03eea08fdd430443ba6052f7e74073ef0ea7edc49b3acdbd1db54ef5dc790715b0f881c7a82275f8cace399a6077001c5db4de837895090d"
//签名
sign := "30450220095dba8efdf41b9054f975b3ba29cdaba818eedd9e99b96e8a36c82a408deea9022100db2638b81bdd005118b043a921ee4d2d16f9e93ce307f5c3e8ed8469b5f884c4"
//java加签 go验签
//使用x509库中的方法将公钥反解出来
publicKey, _ := x509.ReadPublicKeyFromHex(qHex)
//编码未字节数组
asn1, err := hex.DecodeString(sign)
if err != nil {
log.Errorf("签名解码失败,%v", err)
return
}
//java已使用的asn1编码,此处无需转换
verify := publicKey.Verify([]byte(text), asn1)
log.Infof("验签结果:%t\n", verify)
//java加密 go解密
//将私钥反解出来
privateKey, err2 := x509.ReadPrivateKeyFromHex(dHex)
//密文的字节数组
ciphertext, err := hex.DecodeString(encryptHex)
if err != nil {
log.Errorf("密文解码失败,%v", err)
}
//使用c1c3c2模式解密
plaintxt, err2 := sm2.Decrypt(privateKey, ciphertext, sm2.C1C3C2)
if err2 == nil {
log.Infof("解密出的文本为:%s\n", plaintxt)
} else {
log.Infof("解密失败\n")
}
}
java端使用简单编码方式(加解密不受影响):
sm2.setEncoding(new PlainDSAEncoding());
//java加签 go验签
//java未使用的asn1编码,此处在方法内部转换
publicKey, err := x509.ReadPublicKeyFromHex(qHex)
if err != nil {
log.Errorf("公钥解析失败%v", err)
return
}
asn1, err := util.RsPlainToAsn1(sign)
if err != nil {
log.Errorf("转换为asn1编码失败%v", err)
return
}
//textByte, err := hex.DecodeString(text)
if err != nil {
log.Errorf("16进制解码失败%v", err)
return
}
verify := publicKey.Verify([]byte(text), asn1)
if err != nil {
log.Errorf("签名解码失败,%v", err)
return
}
log.Infof("验签结果:%t\n", verify)
// 签名的序列化方式改asn1
func RsPlainToAsn1(sign string) ([]byte, error) {
rsLen := 32
b, _ := hex.DecodeString(sign)
r := new(big.Int).SetBytes(b[0:rsLen])
s := new(big.Int).SetBytes(b[rsLen : rsLen*2])
a, err := asn1.Marshal(sm2Signature{r, s})
if err != nil {
return nil, errors.New("序列化失败")
}
return a, nil
}
go加密、加签,java解密、验签
func main() {
//原文
text := "aaaa"
//私钥
dHex := "008aef670bcb542dd17a43e0a3a5c51860739eca85fc464b276baf0b3c5951f2c1"
//公钥
qHex := "0468fcb75779d1ce6a5a9da7293db8d1bc4af2f40119f4b9fca28bcf02321ecd65c0fc55bef351135fd326faff9ab3c9f2bb2565f35758fcac340e0804e68060f5"
//java加签 go验签
publicKey, err := x509.ReadPublicKeyFromHex(qHex)
if err != nil {
log.Errorf("公钥解析失败%v", err)
return
}
//不使用asn1编码
encrypt, _ := sm2.Encrypt(publicKey, []byte(text), nil, sm2.C1C3C2)
log.Infof("加密内容为:%v", hex.EncodeToString(encrypt))
//java加密 go解密
//将私钥反解出来
privateKey, _ := x509.ReadPrivateKeyFromHex(dHex)
//sign方法默认使用asn1编码
sign, err := privateKey.Sign(rand.Reader, []byte(text), nil)
if err != nil {
log.Errorf("加签失败:%v", err)
}
log.Infof("签名数据:%v", hex.EncodeToString(sign))
}
public static void main(String[] args) {
//需要加密的明文
String text = "aaaa";
//私钥
String dHex = "008aef670bcb542dd17a43e0a3a5c51860739eca85fc464b276baf0b3c5951f2c1";
//公钥
String qHex = "0468fcb75779d1ce6a5a9da7293db8d1bc4af2f40119f4b9fca28bcf02321ecd65c0fc55bef351135fd326faff9ab3c9f2bb2565f35758fcac340e0804e68060f5";
//密文
String encryptStr = "04c1e1f1b52a871d643eeb48b523669e2a1a805468522c8453e49bfd36a43dfece3ce5e254be4a2c6c1af1248ea50b8e459cff1330ca3579d6b48102c31c76415c380697f4de7fba4d8c2d45d4ff04db2163c435ece586bb6dd4f0f33e5ae74babdec38a28";
//签名
String sign = "3046022100fafd79f3b6748c4269590db1d66fa6e7b0e3d03364984c0a2bf44595fdb2b61d022100c58416602b1f960474b27ffccefd0ed165cc24d64a0de287f452cc2e2aa1bcff";
//创建sm2 对象
SM2 sm2 = SmUtil.sm2(dHex,qHex);
//设置加密时,密文的结构是c1c3c2
sm2.setMode(SM2Engine.Mode.C1C3C2);
//设置使用标准格式编码,即使用asn1编码方式 如果使用PlainDSAEncoding,密文/签名直接转为字节数组
sm2.setEncoding(new StandardDSAEncoding());
//使用sm3生成摘要
sm2.setDigest(new SM3Digest());
//得到明文对应的字节数组
String decryptStr = sm2.decryptStr(encryptStr, KeyType.PrivateKey);
System.out.println("解密数据:"+decryptStr);
// 校验 验签
boolean verify = sm2.verify(text.getBytes(), HexUtil.decodeHex(sign));
System.out.println("验签结果"+verify);
}
ts加密、加签、sm4解密,golang解密、验签、sm4加密,java解密
前提:ts的密文会把04去掉 go/java需要在密文最前面补04
import "./styles.css";
import { useEffect } from 'react'
import { sm2, sm3, sm4 } from 'sm-crypto'
const SystemSettings: React.FC = () => {
useEffect(() => {
const cipherMode = 1 // 1 - C1C3C2,0 - C1C2C3,默认为1
//原文
let msgString = "aaaa"
//私钥
let privateKey = "008aef670bcb542dd17a43e0a3a5c51860739eca85fc464b276baf0b3c5951f2c1"
//公钥
let publicKey = "0468fcb75779d1ce6a5a9da7293db8d1bc4af2f40119f4b9fca28bcf02321ecd65c0fc55bef351135fd326faff9ab3c9f2bb2565f35758fcac340e0804e68060f5"
let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode) // 加密结果
let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) // 解密结果
let sigValueHex = sm2.doSignature(msgString, privateKey, {
hash: true,
der: true
}) // 签名
let verify = sm2.doVerifySignature(msgString,sigValueHex,publicKey,{
hash: true,
der: true
}
)
console.log("密文为:"+encryptData)
console.log("签名为:"+sigValueHex)
console.log("解密结果为:"+decryptData)
console.log("验签结果为:"+verify)
const decryptStr = '4ff374c836c052a9b95daeae722bb943' // 可以为 utf8 串或字节数组
const key = '31323334353637383930616263646566' // 可以为 16 进制串或字节数组,要求为 128 比特
let sm4DecryptData = sm4.decrypt(decryptStr, key)
// 解密,默认输出 utf8 字符串,默认使用 pkcs#7 填充(传 pkcs#5 也会走 pkcs#7 填充)
console.log("sm4解密后的数据为:"+sm4DecryptData)
}, [])
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
export default SystemSettings
ts加密(加密代码见上条),java解密
public static void main(String[] args) {
SM4 sm4 = SmUtil.sm4("1234567890abcdef".getBytes(StandardCharsets.UTF_8));
//java解密
String decryptStr = sm4.decryptStr("4ff374c836c052a9b95daeae722bb943");
System.out.println(decryptStr);
}
六、约定
编码约定:
- 所有签名数据,使用der编码(asn.1)
- 签名时的摘要算法,使用sm3
- 公钥和私钥统一使用16进制字符串
- 密文和签名数据使用16进制字符串
- 传输数据不能使用base64编码/bcd码字符串
密文Mode约定:
- 密文mode使用C1C3C2编码
密钥/密文/签名约定
- java和go直接使用提供的代码即可(java使用bc库,go使用gmsm)
- ts和go直接,需要在密文前,拼接04字符串