当前位置: 首页 > news >正文

数据安全系列7:常用的非对称算法浅析

传送门

数据安全系列1:开篇

数据安全系列2:单向散列函数概念

数据安全系列3:密码技术概述

数据安全系列4:密码技术的应用-接口调用的身份识别

数据安全系列5:常用的对称算法浅析

数据安全系列6:从SM4国密算法谈到Bouncy Castle

再谈Bouncy Castle

在上一节里面通过BouncyCastle对SM4的实现初步了解它的强大:什么是BouncyCastle!

A brief history of the Legion of the Bouncy Castle

Originally, in the late 1990s, the Legion of the Bouncy Castle was simply a number of individuals united both in their interests of Cryptography and Open Source. The first official release of the Bouncy Castle APIs appeared in May 2000 and was about 27,000 lines long. The project grew steadily with a C# version of the Java APIs being added in 2006. By 2012 with the Java code base well past 300,000 lines and the C# one over 140,000 it started becoming obvious that a bit more organisation was required to maintain both progress and the quality of the APIs.

Part of this lead to a rewrite of some of the Java APIs, and subsequently the C# ones, to deal with some of the decisions that were made earlier which, in hindsight, were not so good. This slowed down the expansion of the code base a bit by capturing important operations used throughout the higher level protocols such as CMS, TSP, CRMF, and PGP into a set of interfaces. Simplifying the code in itself however was clearly not enough and a not-for-profit association was established in order to provide a more grounded approach to the group's efforts and officially registered in October 2013. In November 2013 the association, officially Legion of the Bouncy Castle Inc., was granted Charity status by the Australian Charities and Not-for-profits Commission (ACNC) on the basis of its work contributing to advancement in education and on the basis that its work was beneficial to the community at large. Further to this the charity was also registered as a fundraiser by Consumer Affairs Victoria allowing it to accept donations to further the charity's work in improving the APIs and their usability by being able to raise funds for certifications such as FIPS.

That, of course, brings us to the present day, and, as you have probably guessed, the story is not over! 

*As of June 2021, Crypto Workshop is a part of Keyfactor.

 这里再补充一下它的官方地址Bouncy Castle 官方网站

除了SM4、AES这些常用的对称算法外,BouncyCastle对非对称算法的支持也非常完善。从最常用的RSA到国密SM2这种加密、加签为一体的算法,到单独加签的的DSA、密钥交换EDCH也不再话下:

Bouncy Castle 的功能非常庞大,主要包括以下几个方面:

1. 丰富的对称加密算法:

  • AES, DES, Triple-DES, Blowfish, Twofish, CAST5, RC2, RC4, RC5, RC6, IDEA, ChaCha, Salsa20, SM4 等。

2. 丰富的非对称加密与密钥协商算法:

  • RSA, DSA, DH (Diffie-Hellman), ECDSA (椭圆曲线数字签名算法), ECDH (椭圆曲线 Diffie-Hellman 密钥协商), ElGamal, SM2 (中国国密算法) 等。

3. 丰富的消息摘要(哈希)算法:

  • MD5, SHA-1, SHA-2家族(SHA-224, SHA-256, SHA-384, SHA-512), SHA-3, RIPEMD, Tiger, Whirlpool, SM3 (中国国密算法) 等。

4. 消息认证码(MAC)算法:

  • HMac (基于各种哈希算法, 如 HMac-SHA256), CMAC, GMAC, Poly1305 等。

5. 对密码学标准和格式的广泛支持(这是其一大亮点):

  • X.509 证书:生成、解析、转换(PEM/DER)、验证(CRL, OCSP)。

  • PKCS#7/CMS 和 S/MIME:用于加密和签名消息的标准。

  • PKCS#10:证书签名请求(CSR)。

  • PKCS#12:个人信息交换格式(.p12, .pfx 文件)。

  • OpenPGP:对 PGP 密钥环、加密、签名消息的完整支持。

  • ASN.1:编解码和处理能力。

  • TLS/SSL:提供了轻量级的 API 用于构建 TLS 引擎(虽然通常不直接用于替换 JSSE,但可用于特殊场景)。

6. 轻量级密码学 API(Lightweight API):

  • 除了作为 JCE 提供商,Bouncy Castle 还提供了一套独立的、不依赖于 JCE 的轻量级 API。这套 API 设计得更简单、更直接,适用于那些不希望引入整个 JCE 复杂性的场景,尤其是在资源受限的环境(如 Android 早期版本)中非常有用。

在看BouncyCastle对非对称的具体实现之前,就先来探讨一下非对称算法相关概念

什么是非对称算法

在定义非对称算法之前,先回顾一下对称算法流程:

上面这种加密、解密都用同一个密钥的使用方法,称为对称加密!这种方式的一个大问题就在于密钥配送问题

密钥配送问题

对称加密算法要依赖于密钥:发送方用密钥对明文进行加密得到密文,接收方用密钥对密文解密得到明文,这个一个可逆的过程。现代加密算法的基石就在于对密钥的保护,所以对于这类需要共享密钥的对称加密算法来说密钥的配送问题就是重中之重了。

而密钥的配送是指,如何安全的将密钥传输给对方(为什么不说是发送方传输给接收方,因为密钥不一定是发送方生成,也有可能是接收方生成),尤其是在网络传输中。一旦密钥生成之后,在将密钥分发到使用方过程中,有一种针对密钥的攻击,通过下面的流程图来理解一下:

图片来源《  图解密码技术 》
  • Alice作为发送者要通过密钥对消息进行加密,然后传输密文给接收者Bob
  • Bob作为接收者得到密文,通过密钥解密得到明文
  • Eve作为"黑客"窃听了通信过程,如果单独获取了Alice发送的密文其实也不要紧,因为没有密钥也解不出来明文。但是Eve是有可能会在这个过程将Alice发送给Alice的密钥也获取到了,那攻击自然就成功了!

但是Alice又不得不把密钥发送给Bob,不发Bob也解不出来。发吧,又怕被Eve截获,正所谓进退两难。密码必须要发送,但又不能发送,这就是对称密码的密钥配送问题!

密钥配送问题的解决方案

解决密钥配送的方案有不少,比如下面几种:

  • 事先共享密钥
  • 密钥分配中心KDC
  • Diffie-Hellman密钥交换技术
  • 非对称算法

这里不对上述4种密钥配送方案做展开讨论,只重点关注非对称算法如何解决密钥配送问题。

非对称算法如何解决密钥配送问题

从上面的讨论不难发现,密钥配置问题的根源在于密钥共享:共享的密钥在配送过程中可能被泄露

所以非对称算法从这一点入手,彻底避免了密钥共享的问题:分离加密密钥与解密密钥,加密密钥称为公钥,解密密钥称为私钥。公钥(Public Key)可以被公共,私钥(Private Key)不被并公开。只要拥有加密密钥,任何人都可以进行加密,但没有解密密钥是无法解密的。这样就意味着公钥不仅能被公开也能被共享,只有拥有解密密钥的人才能解密。所以在非对称算法的场景下,流程就变了:

  • 接收者事先将加密密钥发送给发送者,这个加密密钥即便被窃听者获取也没有问题(这里先不考虑中间人对公钥攻击问题)
  • 发送者使用加密密钥对通信内容进行加密并发送给接收者,而只有拥有解密密钥的人(即接收者本人)才能够进行解密

这样一来,就用不着将解密密钥配送给接收者了,也就是说,对称密码的密钥配送问题,可以通过使用公钥密码来解决。

公钥加密-私钥解密

其实非对称算法是相关AES、SM4这些对称密码来定义,它还有一个名字叫公钥密码。由于无须配送私钥,整个流程就会与上面有些许区别:

  • 发送者只需要加密密钥
  • 接收者只需要解密密钥
  • 私钥不可以被窃听者获取
  • 公钥被窃听者获取也没问题

当然要声明的是这里私钥理论上是不会出现在配送中,但是如果由于管理不善导致私钥泄露也是非常危险的,密钥的管理也是一个很重要的分支。

公钥通信流程图

所以在非对称算法的场景下,整个流程就变成了下面这个图:

图片来源《  图解密码技术 》

公钥密码的历史

RSA研发与制定

        1976年,Whitfield Diffie 和 Martin Hellman 发表了关于公钥密码的设计思想。尽管他们没有提出具体的公钥密码算法,但他们提出了应该将加密密钥和解密密钥分开,而且还描述了公钥密码应该具备的性质。
        1977年,Ralph Merkle和Martin Hellman共同设计了一种具体的公钥密码算法Knapsack。该算法申请了专利,但后来被发现并不安全。

        1978年,Ron Rivest、Adi Shamir和 Reonard Adleman 共同发表了一种公钥密码算法-RSA。RSA 可以说是现在公钥密码的事实标准。
此外,公钥密码还有一些鲜为人知的历史。
        20世纪 60年代,英国电子通信安全局CESG(Communications Electrionic Security Group的 James Ellis 就曾经提出了与公钥密码相同的思路。1973年,CESG的 Clifford Cocks 设计出了与RSA相同的密码,并且在1974年,CESG的MalcolmWiliamson也设计出了与 Deffie-Hellman 算法类似的算法。然而,这些历史直到最近才被公诸于世。

-------------------------------------------------------------------------引自公钥历史

所以RSA算法出现是非常早的,而国密SM2则晚一些:

SM2研发与制定(约2006年 - 2010年)
  • 立项与研究:基于以上背景,国家密码管理局组织国内顶尖的密码学专家和科研机构(如中国科学院、清华大学、山东大学等),启动了国产商用密码算法的研制工作。椭圆曲线密码因其优越性,被选为非对称算法的技术路线。

  • 自主设计曲线:这是SM2算法的核心。与国际上常用的NIST等机构推荐的椭圆曲线(如secp256r1)不同,中国密码专家独立设计了椭圆曲线的参数,包括有限域、椭圆曲线方程系数、基点等。这一举措确保了算法的自主性和可控性,避免了直接使用国外标准曲线可能存在的潜在风险。

  • 形成标准:经过多年的理论分析、安全评估和工程实现,一套完整的基于椭圆曲线的非对称算法标准最终成型。它包含了:

    • 数字签名算法

    • 密钥交换协议

    • 公钥加密算法

  • 正式发布2010年12月17日,国家密码管理局发布了 《GM/T 0003-2012 SM2椭圆曲线公钥密码算法》 系列标准(最初为行业标准,后不断更新)。从此,“SM2”这个名称正式登上历史舞台。“SM”是“商密”的拼音首字母。

常用的非对称算法

RSA

RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的,1983年麻省理工学院在美国为RSA算法申请了专利

至此现在来正式定义一下RSA算法:RSA算法是一种非对称加密算法,与对称加密算法不同的是,RSA算法有两个不同的密钥,一个是公钥,一个是私钥。

RSA是被研究得最广泛的公钥算法,从提出后经历了各种攻击的考验,逐渐为人们接受,普遍认为是目前最优秀的公钥方案之一,被广泛应用于数字签名、身份认证等领域。下面就通过例子来感受一下如何使用RSA。

密钥的生成
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;public class RsaUtil {public static KeyPair generateRsaKeyPair(int keySize) throws NoSuchAlgorithmException {// 使用JCA的KeyPairGeneratorKeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");// 初始化密钥长度(推荐至少2048位)keyPairGenerator.initialize(keySize);// 生成密钥对return keyPairGenerator.generateKeyPair();}
}

因为JAVA库默认支持RSA,所以直接可以生成RSA密钥,如果要用第三方库,比如BouncyCastle,也可以直接在构造函数中指定:

static {// 添加 Bouncy Castle 提供商Security.addProvider(new BouncyCastleProvider());}public static KeyPair generateRsaKeyPair(int keySize) throws Exception {// SunRsaSign 是Java自带的RSA实现提供者// BC 是Bouncy Castle的提供者return generateRsaKeyPairWithProvider("BC", keySize);}public static KeyPair generateRsaKeyPairWithProvider(String provider, int keySize) throws Exception {// 使用JCA的KeyPairGeneratorKeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", provider);// 初始化密钥长度(推荐至少2048位)keyPairGenerator.initialize(keySize);// 生成密钥对return keyPairGenerator.generateKeyPair();}
加密
public static byte[] encrypt(String plainText, PublicKey publicKey) throws Exception {// 使用JCE的Cipher类进行加密Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");cipher.init(Cipher.ENCRYPT_MODE, publicKey);return cipher.doFinal(plainText.getBytes());}
解密
public static byte[] decrypt(byte[] cipherText, PrivateKey privateKey) throws Exception {// 使用JCE的Cipher类进行解密Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");cipher.init(Cipher.DECRYPT_MODE, privateKey);return cipher.doFinal(cipherText);}

运行一下测试用例代码:

public static void main(String[] args) {try {// 生成RSA密钥对KeyPair keyPair = generateRsaKeyPair(2048);PublicKey publicKey = keyPair.getPublic();PrivateKey privateKey = keyPair.getPrivate();// 打印密钥信息System.out.println("公钥格式: " + publicKey.getFormat());System.out.println("私钥格式: " + privateKey.getFormat());System.out.println("公钥算法: " + publicKey.getAlgorithm());System.out.println("私钥算法: " + privateKey.getAlgorithm());// 以Base64格式输出密钥String publicKeyBase64 = Base64.getEncoder().encodeToString(publicKey.getEncoded());String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKey.getEncoded());System.out.println("\n=== 公钥 ===");System.out.println(publicKeyBase64);System.out.println("\n=== 私钥 ===");System.out.println(privateKeyBase64);String originalText = "Hello, RSA!";System.out.println("Original Text: " + originalText);// 加密byte[] encryptedData = encrypt(originalText, publicKey);System.out.println("Encrypted Data (Base64): " + Base64.getEncoder().encodeToString(encryptedData));// 解密byte[] decryptedData = decrypt(encryptedData, privateKey);String decryptedText = new String(decryptedData);System.out.println("Decrypted Text: " + decryptedText);} catch (Exception e) {e.printStackTrace();}}

输出结果格式(因为生成的密钥会不一样,输出会不一样):

公钥格式: X.509
私钥格式: PKCS#8
公钥算法: RSA
私钥算法: RSA

=== 公钥 ===
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg1ufWMkjywpMiaCuITyTXwJT+5RdSAlcaz/l7w7XwXAoYdhMOlrdG3It+XZWLy4gfYli0COVjROI5NA8hE/JgIcpyn+Ke0XNnN5VbgkHnHYLxyp186xIS4MvIJjBk46ramWi2Grf8uQTrOJpJ/SJ0VMGxOEbbv1AVPdtaWS0bz19KIChbC513dT9/wxtK1SQKu7hfUyja4dNNgwoVURafu+0QjCBmxn9tOb9Cos1mdJ2Zi9mydsTYueNWXlRhTnlZIrld0ET8gOopRCxTeDj7D7pCVXexQJgdlnwEjWMrBsjN5WnYeHpzbZXcbn615/indDvYorMcd44gLjI8ABpBQIDAQAB
=== 私钥 ===
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCDW59YySPLCkyJoK4hPJNfAlP7lF1ICVxrP+XvDtfBcChh2Ew6Wt0bci35dlYvLiB9iWLQI5WNE4jk0DyET8mAhynKf4p7Rc2c3lVuCQecdgvHKnXzrEhLgy8gmMGTjqtqZaLYat/y5BOs4mkn9InRUwbE4Rtu/UBU921pZLRvPX0ogKFsLnXd1P3/DG0rVJAq7uF9TKNrh002DChVRFp+77RCMIGbGf205v0KizWZ0nZmL2bJ2xNi541ZeVGFOeVkiuV3QRPyA6ilELFN4OPsPukJVd7FAmB2WfASNYysGyM3ladh4enNtldxufrXn+Kd0O9iisxx3jiAuMjwAGkFAgMBAAECggEAK7wJpcnyPNvE992jnDVK68seoxiWpjFXr+2qZJmQhDsPduRJHPS7wTOW0wPvVe4Jq0Jev+XZGQtz/JrIS1U1RbG63EzyknB/MMdlsikXe6Rh74HXm7W0rE+VFUlGyhYqRFZqjZunzHrbgigv0rm8WRFHo60/MQjormFi8EAzQwW9UR9RpUl4Gy+HCQc/op69IBp71FJ2OzlrSa9KpEtXIVlgCY+NW4LEeZqMyq14eBr1oaZgw9rrviUdSG+GuQmShNyya49FbPEAw217TXCXyU9SSKcLxaAAKqkjyy0gbp0aeqYkWkRzuuxjYECe62tEPQPOLiaOLGDiWEh+HzCogQKBgQDgl8FGj6f0WDtu32MGarwJzc8raHHiZfzaOx8TKmrBqPnU7JMOr2W9Dfe+NAdPw5C+uCcbKag2/8FL34H+qbyTuux4LZX/Va7N0BkAuvlmq+Dlrfb7gqmVpI75CQve6ypKgg4rX1YJGPknDAUOPHkA0zr8i3RdWSycKt1uNBUvXQKBgQCVuiG7w3m5jESjP/gkIzauFGNKWHPy10XDDWLTJH3DTH+MDBTZnQCDVZ4HimqE3L1bLL3iSjpHy9bDl/HLva+V6pQQXU/WkUZJZH+QuK8EzucBPtJsSQN0HBEYfiWlu1aTocPA5cTrpdxqxR1CEGch5MWXZhKtAkXIJ10KBOiNyQKBgQCgMl6AEBPCq/SontopR5ScgZqV4cfprslud+EwcH75Z6fcAmrafK1a7k2Z886LTHlAMGZkb5A2urKLhf9ZQITr8tac2hc1q04mK5rJ9JPUciDWcwHKYGbKADii1ZsMBD2EK8Qjl7rsfk2pVl98yhGdTw2x+V6k/uhc9A5BfxDlWQKBgEQEhoaRyp5sBlJTIRrplFGImYKQpDvRZ7OwRRC+JxOtkNPCOUaSeEUxg9TRIIqy7KKatLg0GD29cBby07lGtplYM2MrwLUs0c1NhlXsUDXS+EYsj9L5aEQ669lAlUGw1SfD+PWMa+TROEyYty3n3bIcB1c724gU3DaNC0sd57dhAoGAW7mv0ZTAbHupx55GPup1GTuRZqLuJ3eUbZoINyTZctWJQzYWQQQ7WRKhD8qf2M2PFVPbr56pB7VMhPX9aLjKyN2SuZuS2CMWI7dnpE3sqArkDyRBifDMRjAUfIpiEU232phUlER6Uo0Q9x3BBFFNmGjD48AAvPQbACIqm5rjf8Q=


Original Text: Hello, RSA!
Encrypted Data (Base64): LOxCHfhfdQ4cH/OZjQjx30NjHDrJ5Gm2U+6UVxKnSaAS1AFbSx48/eitBOWJ99ap98+KqGtNFzLX6g4E+ttXOXwgREl13d3fRGMJHwAYIFlZuyqR3AY9WCQoaSmHt8dvGWNdpI+D/TrGMvv6+RfWeL0sV4yGYA2pPyz/G/WeCdur8N7oG/P8HN1mFanboQTAEotzzCKrpCYpim80iujiWLNCj66c9Wv4dYGe79bxjR7VP0HoesKX1f0P5Ojqb73AEbZzFZcrjiot1JZ3wPjLSNo7d0eYOeY8LeyHFPRvFA0c/ReUTlUcDlgJvSm22wjqn2trjBz+7C0pvGM1UC9grA==
Decrypted Text: Hello, RSA!

通过输出会发现,RSA加密出来的密文长度不仅非常长,连它本身的密钥长度也很长,这其实是由它的底层算法决定的。稍稍改下上面的测试代码,添加打印出密钥的字符串长度:

// 忽略其它代码....
// 初始化密钥长度(推荐至少2048位)
keyPairGenerator.initialize(keySize);// 忽略其它代码....
System.out.println("\n=== 公钥 ===,长度" + publicKeyBase64.length());System.out.println("\n=== 私钥 ===,长度" + privateKeyBase64.length());

输出结果:

=== 公钥 ===,长度392

=== 私钥 ===,长度1628

这里指定的2048就是RSA密钥的实际长度,单位是“位”(bit),即生成的公钥和私钥的模数(n)长度为2048位,这是目前推荐的安全长度。最终密钥字符串长度(Base64编码后)会因密钥格式和编码方式略有不同,但对于2048位RSA密钥:

  • 公钥(X.509格式,Base64编码)长度约为 392 字符
  • 私钥(PKCS#8格式,Base64编码)长度约为 1674 字符

私钥长度为可能是因为使用了不同的Provider(如Bouncy Castle),它生成的PKCS#8编码格式与SunRsaSign略有差异,导致Base64编码后的字符串长度不同。密钥内容和编码方式(如是否有额外参数或头信息)都会影响最终长度,但密钥强度和安全性是一样的。不同实现生成的密钥长度在1600~1700字符之间都属于正常现象

公钥格式

在上面测试中输出了公钥与私钥的格式,看下源码的方法及注释:

    /*** Returns the name of the primary encoding format of this key,* or null if this key does not support encoding.* The primary encoding format is* named in terms of the appropriate ASN.1 data format, if an* ASN.1 specification for this key exists.* For example, the name of the ASN.1 data format for public* keys is <I>SubjectPublicKeyInfo</I>, as* defined by the X.509 standard; in this case, the returned format is* {@code "X.509"}. Similarly,* the name of the ASN.1 data format for private keys is* <I>PrivateKeyInfo</I>,* as defined by the PKCS #8 standard; in this case, the returned format is* {@code "PKCS#8"}.** @return the primary encoding format of the key.*/public String getFormat();// 下面是方法注释的翻译,也附在这里了/**返回此密钥的主要编码格式名称,如果不支持编码则返回 null。
主要编码格式以相应的 ASN.1 数据格式命名,如果该密钥存在 ASN.1 规范。
例如,公钥的 ASN.1 数据格式名称是 SubjectPublicKeyInfo,
按照 X.509 标准定义,此时返回的格式为 "X.509"。
类似地,私钥的 ASN.1 数据格式名称是 PrivateKeyInfo,
按照 PKCS #8 标准定义,此时返回的格式为 "PKCS#8"。@return 密钥的主要编码格式名称。 */ 
X.509

X.509是一种用于公钥证书的标准格式,最初用于SSL/TLS等安全协议。它定义了证书的结构,包括公钥、持有者信息、签名等。X.509公钥通常以ASN.1结构编码,常见编码方式有DER(二进制)和PEM(Base64带头尾标识)。
在Java中,公钥的getFormat()方法返回X.509,表示其采用X.509标准格式。X.509格式的公钥可用于身份验证、加密等场景。

补充说明:

  • DER:二进制编码
  • PEM:Base64编码并带有头尾标识(如-----BEGIN PUBLIC KEY-----)
私钥格式
PKCS#8

PKCS#8 定义了一种标准化的、与算法无关的格式,用于存储和传输私钥信息。在 PKCS#8 出现之前,不同的加密算法(如 RSA、DSA、EC)有自己特定的私钥存储格式(例如 PKCS#1 用于 RSA)。这导致在需要支持多种算法的系统中,管理和处理私钥变得非常复杂。PKCS#8 解决了这个问题,它提供了一个通用的“信封”或“容器”,可以将任何类型的私钥及其相关属性打包在一起

一个实际的例子

当在用代码仓库管理代码的时候,一般会要求进行认证,比如国内的gitee这个平台:

gitee-SSH 公钥设置

它提供了SSH 公钥设置的方法:SSH 公钥设置(这里有详细的操作指引)

所以这里也通过ssh-keygen命令来测试一下,看看生成出来的密钥对,这里选择rsa算法,就不用它要求的Ed25519 (推荐)。

首先也在window上打开Windows PowerShell(操作系统自带)的,然后输入命令:

ssh-keygen -t rsa -C "rsa test" -f test_rsa

三次回车以后,会在对应目录生成密钥文件,然后就可以查看到对应的密钥了

类型命令示例特点
Ed25519 (推荐)ssh-keygen -t ed25519安全、高效、密钥短
RSA 4096ssh-keygen -t rsa -b 4096兼容性好,但密钥较长
签名
加签
验签

SM2

从SM4引出前面一节国密算法时,列出当前的国密算法:SM1、SM4、SM7是对称算法,其中SM4为常用算法。SM3为摘要算法(也称杂凑:杂凑运算)。它也提供了非对称算法,就是SM2算法。同讨论RSA一样,下面就通过例子来感受一下如何使用SM2。

密钥的生成
import org.bouncycastle.jce.provider.BouncyCastleProvider;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;/*** @author * @date 2025/10/13*/
public class Sm2Util {static {// 添加 Bouncy Castle 提供商Security.addProvider(new BouncyCastleProvider());}// SM2曲线参数private static final String SM2_CURVE_NAME = "sm2p256v1";/*** 生成SM2密钥对(标准JCA方式)*/public static KeyPair generateKeyPair() throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");keyPairGenerator.initialize(new ECGenParameterSpec(SM2_CURVE_NAME), new SecureRandom());return keyPairGenerator.generateKeyPair();}
}

因为JAVA库默认不支持SM2,所以要用第三方库,比如BouncyCastle,关于BouncyCastle在前面介绍过:数据安全系列6:从SM4国密算法谈到Bouncy Castle。      

加密
private static final ECNamedCurveParameterSpec SM2_CURVE_PARAMS;private static final ECDomainParameters DOMAIN_PARAMS;static {SM2_CURVE_PARAMS = ECNamedCurveTable.getParameterSpec(SM2_CURVE_NAME);DOMAIN_PARAMS = new ECDomainParameters(SM2_CURVE_PARAMS.getCurve(),SM2_CURVE_PARAMS.getG(),SM2_CURVE_PARAMS.getN(),SM2_CURVE_PARAMS.getH());}/*** SM2加密(使用PublicKey接口)*/public static byte[] encrypt(PublicKey publicKey, byte[] data) throws CryptoException {return encrypt((BCECPublicKey) publicKey, data);}/*** SM2加密(使用BCECPublicKey)*/public static byte[] encrypt(BCECPublicKey publicKey, byte[] data) throws CryptoException {ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(publicKey.getQ(), DOMAIN_PARAMS);return encrypt(publicKeyParams, data);}/*** SM2加密核心实现*/private static byte[] encrypt(ECPublicKeyParameters publicKeyParams, byte[] data) throws CryptoException {SM2Engine engine = new SM2Engine();engine.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));return engine.processBlock(data, 0, data.length);}
解密
    /*** SM2解密(使用PrivateKey接口)*/public static byte[] decrypt(PrivateKey privateKey, byte[] encryptedData) throws CryptoException {return decrypt((BCECPrivateKey) privateKey, encryptedData);}/*** SM2解密(使用BCECPrivateKey)*/public static byte[] decrypt(BCECPrivateKey privateKey, byte[] encryptedData) throws CryptoException {ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(privateKey.getD(), DOMAIN_PARAMS);return decrypt(privateKeyParams, encryptedData);}/*** SM2解密核心实现*/private static byte[] decrypt(ECPrivateKeyParameters privateKeyParams, byte[] encryptedData) throws CryptoException {SM2Engine engine = new SM2Engine();engine.init(false, privateKeyParams);return engine.processBlock(encryptedData, 0, encryptedData.length);}

运行一下测试用例代码:

import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;/*** SM2 工具类使用示例*/
public class SM2Example {public static void main(String[] args) {try {System.out.println("=== SM2 国密算法完整示例 ===\n");// 1. 生成密钥对System.out.println("1. 生成SM2密钥对...");KeyPair keyPair = SM2Util.generateKeyPair();PublicKey publicKey = keyPair.getPublic();PrivateKey privateKey = keyPair.getPrivate();System.out.println("   公钥类型: " + publicKey.getClass().getSimpleName());System.out.println("   私钥类型: " + privateKey.getClass().getSimpleName());System.out.println("   密钥对生成成功!\n");// 2. 测试数据String originalText = "Hello, 国密SM2算法!这是一段测试文本。";byte[] data = originalText.getBytes("UTF-8");System.out.println("2. 测试数据:");System.out.println("   原文: " + originalText);System.out.println("   数据长度: " + data.length + " 字节\n");// 3. 加密测试System.out.println("3. 加密测试...");byte[] encryptedData = SM2Util.encrypt(publicKey, data);String encryptedHex = SM2Util.bytesToHex(encryptedData);System.out.println("   加密成功!");System.out.println("   密文长度: " + encryptedData.length + " 字节");System.out.println("   密文(Hex): " + (encryptedHex.length() > 100 ? encryptedHex.substring(0, 100) + "..." : encryptedHex) + "\n");// 4. 解密测试System.out.println("4. 解密测试...");byte[] decryptedData = SM2Util.decrypt(privateKey, encryptedData);String decryptedText = new String(decryptedData, "UTF-8");System.out.println("   解密成功!");System.out.println("   解密结果: " + decryptedText);System.out.println("   加解密验证: " + originalText.equals(decryptedText) + "\n");// 5. 密钥序列化测试System.out.println("5. 密钥序列化测试...");byte[] publicKeyBytes = SM2Util.getPublicKeyBytes(keyPair);byte[] privateKeyBytes = SM2Util.getPrivateKeyBytes(keyPair);PublicKey restoredPublicKey = SM2Util.bytesToPublicKey(publicKeyBytes);PrivateKey restoredPrivateKey = SM2Util.bytesToPrivateKey(privateKeyBytes);// 使用恢复的密钥进行加密解密byte[] testEncrypted = SM2Util.encrypt(restoredPublicKey, data);byte[] testDecrypted = SM2Util.decrypt(restoredPrivateKey, testEncrypted);String testDecryptedText = new String(testDecrypted, "UTF-8");System.out.println("   密钥序列化验证: " + originalText.equals(testDecryptedText) + "\n");// 6. 签名验签测试System.out.println("6. 签名验签测试...");byte[] signature = SM2SignUtil.sign(privateKey, data);boolean verifyResult = SM2SignUtil.verify(publicKey, data, signature);System.out.println("   签名长度: " + signature.length + " 字节");System.out.println("   验签结果: " + verifyResult + "\n");// 7. SM3哈希测试System.out.println("7. SM3哈希测试...");byte[] sm3Hash = SM2SignUtil.sm3Hash(data);String sm3HashHex = SM2SignUtil.sm3HashHex(data);System.out.println("   SM3哈希长度: " + sm3Hash.length + " 字节");System.out.println("   SM3哈希值: " + sm3HashHex + "\n");System.out.println("=== 所有测试完成 ===\n");System.out.println("8. 生成x,y坐标");// 最简方式:直接在您的代码中使用BCECPublicKey sm2PublicKey = (BCECPublicKey) publicKey;BigInteger x = sm2PublicKey.getQ().getAffineXCoord().toBigInteger();BigInteger y = sm2PublicKey.getQ().getAffineYCoord().toBigInteger();// 如果需要十六进制String xHex = x.toString(16);String yHex = y.toString(16);// 补齐到64位(32字节)xHex = String.format("%64s", xHex).replace(' ', '0');yHex = String.format("%64s", yHex).replace(' ', '0');System.out.println("   x坐标: " + xHex);System.out.println("   y坐标: " + yHex);System.out.println("   公钥: " + SM2Util.getPublicKeyString(publicKey) + " 字节");} catch (Exception e) {System.err.println("执行过程中发生错误: " + e.getMessage());e.printStackTrace();}}
}

输出结果格式(因为生成的密钥会不一样,输出会不一样):

=== SM2 国密算法完整示例 ===

1. 生成SM2密钥对...
   公钥类型: BCECPublicKey
   私钥类型: BCECPrivateKey
   密钥对生成成功!

2. 测试数据:
   原文: Hello, 国密SM2算法!这是一段测试文本。
   数据长度: 52 字节

3. 加密测试...
   加密成功!
   密文长度: 149 字节
   密文(Hex): 0422cafa37c538b82afbaffa65ad364c808205e4de03239581c2aa2e0e172a191fcdee0a86e6cca66dfa8cec660cca77529b...

4. 解密测试...
   解密成功!
   解密结果: Hello, 国密SM2算法!这是一段测试文本。
   加解密验证: true

5. 密钥序列化测试...
   密钥序列化验证: true

6. 签名验签测试...
   签名长度: 71 字节
   验签结果: true

7. SM3哈希测试...
   SM3哈希长度: 32 字节
   SM3哈希值: 9447675a4aaadf855a1665c29ee232510bf70bba9527167b785334a569133af7

=== 所有测试完成 ===

8. 生成x,y坐标
   x坐标: d47a14b70622f9de887eeb4d53236ad80f3497911665e305d754fff029941912
   y坐标: c609f5601355d94b144c6121a7fa50a9815759f944d11ce3f6e995d0537d4caf
   公钥: 04d47a14b70622f9de887eeb4d53236ad80f3497911665e305d754fff029941912c609f5601355d94b144c6121a7fa50a9815759f944d11ce3f6e995d0537d4caf 字节

公钥格式

其实SM2的公钥、私钥格式与RSA一样的,分别也是公钥格式: X.509、私钥格式: PKCS#8。这个也可以通过跟RSA一样的获取方法来查看:

KeyPair keyPair = SM2Util.generateKeyPair();PublicKey publicKey = keyPair.getPublic();PrivateKey privateKey = keyPair.getPrivate();// 打印密钥信息System.out.println("公钥格式: " + publicKey.getFormat());System.out.println("私钥格式: " + privateKey.getFormat());System.out.println("公钥算法: " + publicKey.getAlgorithm());System.out.println("私钥算法: " + privateKey.getAlgorithm());

但是要单独讨论的公钥格式是因为SM2是ECC算法:椭圆曲线密码。这里就会涉及到公钥的坐标:

图片引自网络: https://image.baidu.com/search/detail?adpicid=0&b_applid=6171462335319159717&bdtype=0&commodity=©right=&cs=1374338851%2C2844398770&di=7552572858984038401&fr=click-pic&fromurl=http%253A%252F%252Fblog.51cto.com%252Fu_87851%252F10924811&gsm=3c&hd=&height=0&hot=&ic=&ie=utf-8&imgformat=&imgratio=&imgspn=0&is=0%2C0&isImgSet=&latest=&lid=&lm=&objurl=https%253A%252F%252Fs2.51cto.com%252Fimages%252Fblog%252F202403%252F15234623_65f46d4fbc6f299485.png%253Fx-oss-process%253Dimage%252Fwatermark%252Csize_16%252Ctext_QDUxQ1RP5Y2a5a6i%252Ccolor_FFFFFF%252Ct_30%252Cg_se%252Cx_10%252Cy_10%252Cshadow_20%252Ctype_ZmFuZ3poZW5naGVpdGk%253D%252Fresize%252Cm_fixed%252Cw_1184&os=143967644%2C3135379550&pd=image_content&pi=0&pn=57&rn=1&simid=1374338851%2C2844398770&tn=baiduimagedetail&width=0&word=ecc%E7%AE%97%E6%B3%95%E5%8E%9F%E7%90%86&z=https://image.baidu.com/search/detail?adpicid=0&b_applid=6171462335319159717&bdtype=0&commodity=©right=&cs=1374338851%2C2844398770&di=7552572858984038401&fr=click-pic&fromurl=http%253A%252F%252Fblog.51cto.com%252Fu_87851%252F10924811&gsm=3c&hd=&height=0&hot=&ic=&ie=utf-8&imgformat=&imgratio=&imgspn=0&is=0%2C0&isImgSet=&latest=&lid=&lm=&objurl=https%253A%252F%252Fs2.51cto.com%252Fimages%252Fblog%252F202403%252F15234623_65f46d4fbc6f299485.png%253Fx-oss-process%253Dimage%252Fwatermark%252Csize_16%252Ctext_QDUxQ1RP5Y2a5a6i%252Ccolor_FFFFFF%252Ct_30%252Cg_se%252Cx_10%252Cy_10%252Cshadow_20%252Ctype_ZmFuZ3poZW5naGVpdGk%253D%252Fresize%252Cm_fixed%252Cw_1184&os=143967644%2C3135379550&pd=image_content&pi=0&pn=57&rn=1&simid=1374338851%2C2844398770&tn=baiduimagedetail&width=0&word=ecc%E7%AE%97%E6%B3%95%E5%8E%9F%E7%90%86&z=
1. 核心概念:椭圆曲线与点

SM2使用的是一条特定的椭圆曲线,其方程形式为:

y² = x³ + ax + b (mod p)

其中:

  • (x, y) 是曲线上点的坐标。

  • ab 是椭圆曲线的参数,决定了曲线的形状。

  • p 是一个非常大的质数,所有的运算都在这个质数定义的有限域上进行(即“模 p”运算)。

SM2标准规定了一套固定的参数(包括 abp 等),所有使用SM2的实体都必须使用这套相同的参数,以确保可以互通。

2. 坐标 (x, y) 的来源
a) 基点 G

椭圆曲线密码系统需要一个公开的、大家都认同的起点,称为 基点 (Generator Point),通常用 G 表示。这个基点 G 也有自己的坐标 (Gx, Gy)

SM2的基点G坐标是标准中预定义好的:

  • Gx: 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7

  • Gy: BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0

b) 公钥 P

用户的公钥是椭圆曲线上的一个点 P,它的坐标 (Px, Py) 是通过以下计算得到的:
P = d ₐ × G

  • d ₐ 是用户的 私钥,一个随机生成的秘密大整数。

  • G 是上面提到的基点。

  • × 是 椭圆曲线上的点乘运算,即基点 G 连续加自己 d ₐ 次。

通过这个运算,最终得到一个新的点 P,这个点的坐标 (Px, Py) 就是用户的 公钥

总结:

  • 私钥 (d ₐ):一个秘密的大整数。

  • 公钥 (Px, Py):椭圆曲线上的一个点,由 私钥 × 基点G 计算得出。

在实际SM2应用中除了生成一对密钥来使用外,是有可能直接通过公钥坐标来操作的。下面的代码演示了如何获取(x,y)坐标:

// 最简方式:直接在您的代码中使用BCECPublicKey sm2PublicKey = (BCECPublicKey) publicKey;BigInteger x = sm2PublicKey.getQ().getAffineXCoord().toBigInteger();BigInteger y = sm2PublicKey.getQ().getAffineYCoord().toBigInteger();// 如果需要十六进制String xHex = x.toString(16);String yHex = y.toString(16);// 补齐到64位(32字节)xHex = String.format("%64s", xHex).replace(' ', '0');yHex = String.format("%64s", yHex).replace(' ', '0');System.out.println("   x坐标: " + xHex);System.out.println("   y坐标: " + yHex);

再观察一下上面的测试代码中坐标与公钥输出:

8. 生成x,y坐标
   x坐标: d47a14b70622f9de887eeb4d53236ad80f3497911665e305d754fff029941912
   y坐标: c609f5601355d94b144c6121a7fa50a9815759f944d11ce3f6e995d0537d4caf
   公钥: 04d47a14b70622f9de887eeb4d53236ad80f3497911665e305d754fff029941912c609f5601355d94b144c6121a7fa50a9815759f944d11ce3f6e995d0537d4caf 字节

会发现:x + y = "04" + 公钥!即公钥字符串为 04 || x || y,就是将"04"与x、y坐标对应的密钥字符串拼接起来,而这个也是ECC的另一个特性:

3. 坐标的表示和传输

在实际应用中(如数字证书、加密通信),我们需要将公钥点 (Px, Py) 表示成字节流进行传输或存储。常见的格式有两种:

a) 未压缩格式 (Uncompressed Form)

这是最直接的形式。

  • 格式: 04 || x || y

  • 04 是一个前缀字节,表示后面跟着的是完整的、未压缩的坐标。

  • x 和 y 分别是坐标值的字节表示(大端序)。

  • 长度: 因为x和y通常都是256位(32字节),所以一个未压缩的公钥长度通常是 1 + 32 + 32 = 65 字节。

示例: 04 32C4AE2C... (Px的32字节) BC3736A2... (Py的32字节)

b) 压缩格式 (Compressed Form)

为了节省空间,可以使用压缩格式。因为只要知道 x 坐标,就可以通过曲线方程 y² = x³ + ax + b (mod p) 解出 y。但方程解出来有两个可能的y值(一个奇,一个偶),所以需要额外一个比特来区分。

  • 格式: PC || x

  • PC(压缩标识)是一个前缀字节:

    • 如果 y 是偶数,则 PC = 02

    • 如果 y 是奇数,则 PC = 03

  • x 是x坐标的字节表示。

  • 长度: 1 + 32 = 33 字节。

示例: 02 32C4AE2C... (Px的32字节) 或 03 32C4AE2C... (Px的32字节)

接收方拿到压缩公钥后,可以用 x 坐标代入曲线方程计算出 ,然后开平方得到两个y值,最后根据前缀 02 或 03 来选择正确的那个y值,从而恢复出完整的 (x, y) 坐标。

而公钥是否压缩的代码可以通过下面的代码来指定:

/*** 获取 SM2 公钥的完整字符串表示(04 + X + Y)*/public static String getPublicKeyString(PublicKey publicKey) {if (!(publicKey instanceof BCECPublicKey)) {throw new IllegalArgumentException("公钥必须是 BCECPublicKey 类型");}BCECPublicKey sm2PublicKey = (BCECPublicKey) publicKey;// 直接获取非压缩格式的公钥字节数组// false 参数表示获取非压缩格式(04 + X + Y)// true 参数表示获取压缩格式(02/03 + X)byte[] publicKeyBytes = sm2PublicKey.getQ().getEncoded(false);// 转换为十六进制字符串return Hex.toHexString(publicKeyBytes);}

公钥不仅在加密过程中会使用,也会在密钥交换、数字签名等场景下依赖

总结

这里花了不少篇幅来讨论RSA与SM2的原理及代码,主要聚集在密钥生成、加密、解密等操作上,但其实密钥交换、数字签名等也是它们的典型应用场景,关于这部分后面单独再开一节来讨论。最后通过一个表格来总结一下这两种非对称算法(DS生成),并会在最近附上SM2的完整测试代码。

各自特点总结对比表格

特点维度RSASM2
理论基础大整数分解难题
破解RSA等价于分解两个大质数的乘积N。
椭圆曲线离散对数难题
在椭圆曲线群上,已知基点G和公钥K,求私钥k是困难的。
密钥类型仅使用整数进行运算。使用椭圆曲线上的点(坐标)进行运算。
密钥长度较长。为保证安全,目前推荐使用 2048位 或更长。短得多256位 的SM2私钥,其安全性约等于 3072位 的RSA。
安全性能1. 安全性依赖密钥长度,随着计算能力发展,需不断加长密钥。
2. 对量子计算机攻击脆弱。Shor算法能有效破解。
1. 同等安全下,密钥更短(密钥效率高)。
2. 抗攻击性更强,解决ECDL问题目前没有亚指数时间算法。
3. 同样对量子计算机脆弱,但资源需求相对更高。
计算性能较慢
• 加密/解密、签名/验证速度都相对较慢,因为涉及大数模幂运算。
• 签名验证速度通常快于签名生成
更快
• 在相同的安全强度下,计算速度比RSA快得多(通常快一个数量级)。
• 节省计算资源和功耗,特别适合计算能力受限的环境(如智能卡、物联网设备)。
带宽与存储密钥和密文/签名的数据量较大。数据量小
• 短密钥意味着更小的证书尺寸、更短的签名和密文,节省带宽和存储空间。
标准化与合规性国际标准(如PKCS#1, X.509),全球通用。中国国家密码算法标准(GB/T 32918)。
• 在中国商用密码应用体系中是强制性要求。
• 满足网络安全等级保护(等保2.0)、关键信息基础设施保护等法规的合规要求。
功能设计核心算法只提供非对称加密和签名。其他功能(如密钥交换)需要额外协议(如RSA-OAEP, RSA-PSS)。设计更集成
• 标准本身完整定义了数字签名、密钥交换和公钥加密三大功能,体系完整。
专利与授权专利已过期,可自由使用。中国政府持有算法核心专利,在中国境内免费使用,旨在减少对国外密码技术的依赖。

SM2完整测试代码

package com.tw.tsm.cipher.util.sm2;import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;/*** SM2 国密算法工具类(完整修复版)* 支持密钥生成、加密、解密、签名、验签等功能*/
public class SM2Util {static {// 确保只注册一次BouncyCastle提供者if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {Security.addProvider(new BouncyCastleProvider());}}// SM2曲线参数private static final String SM2_CURVE_NAME = "sm2p256v1";private static final ECNamedCurveParameterSpec SM2_CURVE_PARAMS;private static final ECDomainParameters DOMAIN_PARAMS;static {SM2_CURVE_PARAMS = ECNamedCurveTable.getParameterSpec(SM2_CURVE_NAME);DOMAIN_PARAMS = new ECDomainParameters(SM2_CURVE_PARAMS.getCurve(),SM2_CURVE_PARAMS.getG(),SM2_CURVE_PARAMS.getN(),SM2_CURVE_PARAMS.getH());}public static ECDomainParameters getDomainParams() {return DOMAIN_PARAMS;}// ==================== 密钥生成 ====================/*** 生成SM2密钥对(标准JCA方式)*/public static KeyPair generateKeyPair() throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");keyPairGenerator.initialize(new ECGenParameterSpec(SM2_CURVE_NAME), new SecureRandom());return keyPairGenerator.generateKeyPair();}/*** 生成SM2密钥对(BouncyCastle底层方式)*/public static AsymmetricCipherKeyPair generateKeyPairLowLevel() {ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();ECKeyGenerationParameters keyGenParams = new ECKeyGenerationParameters(DOMAIN_PARAMS, new SecureRandom());keyPairGenerator.init(keyGenParams);return keyPairGenerator.generateKeyPair();}/*** 从底层密钥对转换为JCA KeyPair*/public static KeyPair convertToKeyPair(AsymmetricCipherKeyPair cipherKeyPair) throws Exception {KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");// 转换公钥ECPublicKeyParameters ecPublic = (ECPublicKeyParameters) cipherKeyPair.getPublic();ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(ecPublic.getQ(), new ECParameterSpec(SM2_CURVE_PARAMS.getCurve(), SM2_CURVE_PARAMS.getG(), SM2_CURVE_PARAMS.getN(), SM2_CURVE_PARAMS.getH()));PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);// 转换私钥ECPrivateKeyParameters ecPrivate = (ECPrivateKeyParameters) cipherKeyPair.getPrivate();ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(ecPrivate.getD(),new ECParameterSpec(SM2_CURVE_PARAMS.getCurve(), SM2_CURVE_PARAMS.getG(),SM2_CURVE_PARAMS.getN(), SM2_CURVE_PARAMS.getH()));PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);return new KeyPair(publicKey, privateKey);}// ==================== 加密解密 ====================/*** SM2加密(使用PublicKey接口)*/public static byte[] encrypt(PublicKey publicKey, byte[] data) throws CryptoException {return encrypt((BCECPublicKey) publicKey, data);}/*** SM2加密(使用BCECPublicKey)*/public static byte[] encrypt(BCECPublicKey publicKey, byte[] data) throws CryptoException {ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(publicKey.getQ(), DOMAIN_PARAMS);return encrypt(publicKeyParams, data);}/*** SM2加密(使用公钥字节数组)*/public static byte[] encrypt(byte[] publicKeyBytes, byte[] data) throws CryptoException {ECPoint publicKeyPoint = DOMAIN_PARAMS.getCurve().decodePoint(publicKeyBytes);ECPublicKeyParameters publicKeyParams = new ECPublicKeyParameters(publicKeyPoint, DOMAIN_PARAMS);return encrypt(publicKeyParams, data);}/*** SM2加密核心实现*/private static byte[] encrypt(ECPublicKeyParameters publicKeyParams, byte[] data) throws CryptoException {SM2Engine engine = new SM2Engine();engine.init(true, new ParametersWithRandom(publicKeyParams, new SecureRandom()));return engine.processBlock(data, 0, data.length);}/*** SM2解密(使用PrivateKey接口)*/public static byte[] decrypt(PrivateKey privateKey, byte[] encryptedData) throws CryptoException {return decrypt((BCECPrivateKey) privateKey, encryptedData);}/*** SM2解密(使用BCECPrivateKey)*/public static byte[] decrypt(BCECPrivateKey privateKey, byte[] encryptedData) throws CryptoException {ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(privateKey.getD(), DOMAIN_PARAMS);return decrypt(privateKeyParams, encryptedData);}/*** SM2解密(使用私钥字节数组)*/public static byte[] decrypt(byte[] privateKeyBytes, byte[] encryptedData) throws CryptoException {BigInteger privateKeyD = new BigInteger(1, privateKeyBytes);ECPrivateKeyParameters privateKeyParams = new ECPrivateKeyParameters(privateKeyD, DOMAIN_PARAMS);return decrypt(privateKeyParams, encryptedData);}/*** SM2解密核心实现*/private static byte[] decrypt(ECPrivateKeyParameters privateKeyParams, byte[] encryptedData) throws CryptoException {SM2Engine engine = new SM2Engine();engine.init(false, privateKeyParams);return engine.processBlock(encryptedData, 0, encryptedData.length);}// ==================== 密钥序列化与反序列化 ====================/*** 获取公钥字节数组(非压缩格式)*/public static byte[] getPublicKeyBytes(KeyPair keyPair) {BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();return publicKey.getQ().getEncoded(false);}/*** 获取私钥字节数组*/public static byte[] getPrivateKeyBytes(KeyPair keyPair) {BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();return privateKey.getD().toByteArray();}/*** 从字节数组还原公钥*/public static PublicKey bytesToPublicKey(byte[] publicKeyBytes) throws Exception {try {KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");ECPoint publicKeyPoint = DOMAIN_PARAMS.getCurve().decodePoint(publicKeyBytes);ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(publicKeyPoint, new ECParameterSpec(SM2_CURVE_PARAMS.getCurve(), SM2_CURVE_PARAMS.getG(),SM2_CURVE_PARAMS.getN(), SM2_CURVE_PARAMS.getH()));return keyFactory.generatePublic(publicKeySpec);} catch (InvalidKeySpecException e) {throw new Exception("无效的公钥格式", e);}}/*** 从字节数组还原私钥*/public static PrivateKey bytesToPrivateKey(byte[] privateKeyBytes) throws Exception {try {KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");BigInteger privateKeyD = new BigInteger(1, privateKeyBytes);ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKeyD,new ECParameterSpec(SM2_CURVE_PARAMS.getCurve(), SM2_CURVE_PARAMS.getG(),SM2_CURVE_PARAMS.getN(), SM2_CURVE_PARAMS.getH()));return keyFactory.generatePrivate(privateKeySpec);} catch (InvalidKeySpecException e) {throw new Exception("无效的私钥格式", e);}}// ==================== 辅助方法 ====================/*** 字节数组转十六进制字符串*/public static String bytesToHex(byte[] bytes) {return Hex.toHexString(bytes);}/*** 十六进制字符串转字节数组*/public static byte[] hexToBytes(String hexString) {return Hex.decode(hexString);}/*** 获取SM2曲线参数信息*/public static void printSM2CurveInfo() {System.out.println("SM2曲线名称: " + SM2_CURVE_NAME);System.out.println("曲线阶(n): " + SM2_CURVE_PARAMS.getN());System.out.println("余因子(h): " + SM2_CURVE_PARAMS.getH());}/*** 获取 SM2 公钥的完整字符串表示(04 + X + Y)*/public static String getPublicKeyString(PublicKey publicKey) {if (!(publicKey instanceof BCECPublicKey)) {throw new IllegalArgumentException("公钥必须是 BCECPublicKey 类型");}BCECPublicKey sm2PublicKey = (BCECPublicKey) publicKey;// 直接获取非压缩格式的公钥字节数组// false 参数表示获取非压缩格式(04 + X + Y)// true 参数表示获取压缩格式(02/03 + X)byte[] publicKeyBytes = sm2PublicKey.getQ().getEncoded(false);// 转换为十六进制字符串return Hex.toHexString(publicKeyBytes);}/*** 获取 SM2 公钥的完整字符串表示(04 + X + Y)* 从 KeyPair 中直接获取*/public static String getPublicKeyString(KeyPair keyPair) {return getPublicKeyString(keyPair.getPublic());}
}
package com.tw.tsm.cipher.util.sm2;import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;/*** SM2 工具类使用示例*/
public class SM2Example {public static void main(String[] args) {try {System.out.println("=== SM2 国密算法完整示例 ===\n");// 1. 生成密钥对System.out.println("1. 生成SM2密钥对...");KeyPair keyPair = SM2Util.generateKeyPair();PublicKey publicKey = keyPair.getPublic();PrivateKey privateKey = keyPair.getPrivate();// 打印密钥信息System.out.println("公钥格式: " + publicKey.getFormat());System.out.println("私钥格式: " + privateKey.getFormat());System.out.println("公钥算法: " + publicKey.getAlgorithm());System.out.println("私钥算法: " + privateKey.getAlgorithm());System.out.println("   公钥类型: " + publicKey.getClass().getSimpleName());System.out.println("   私钥类型: " + privateKey.getClass().getSimpleName());System.out.println("   密钥对生成成功!\n");// 2. 测试数据String originalText = "Hello, 国密SM2算法!这是一段测试文本。";byte[] data = originalText.getBytes("UTF-8");System.out.println("2. 测试数据:");System.out.println("   原文: " + originalText);System.out.println("   数据长度: " + data.length + " 字节\n");// 3. 加密测试System.out.println("3. 加密测试...");byte[] encryptedData = SM2Util.encrypt(publicKey, data);String encryptedHex = SM2Util.bytesToHex(encryptedData);System.out.println("   加密成功!");System.out.println("   密文长度: " + encryptedData.length + " 字节");System.out.println("   密文(Hex): " + (encryptedHex.length() > 100 ? encryptedHex.substring(0, 100) + "..." : encryptedHex) + "\n");// 4. 解密测试System.out.println("4. 解密测试...");byte[] decryptedData = SM2Util.decrypt(privateKey, encryptedData);String decryptedText = new String(decryptedData, "UTF-8");System.out.println("   解密成功!");System.out.println("   解密结果: " + decryptedText);System.out.println("   加解密验证: " + originalText.equals(decryptedText) + "\n");// 5. 密钥序列化测试System.out.println("5. 密钥序列化测试...");byte[] publicKeyBytes = SM2Util.getPublicKeyBytes(keyPair);byte[] privateKeyBytes = SM2Util.getPrivateKeyBytes(keyPair);PublicKey restoredPublicKey = SM2Util.bytesToPublicKey(publicKeyBytes);PrivateKey restoredPrivateKey = SM2Util.bytesToPrivateKey(privateKeyBytes);// 使用恢复的密钥进行加密解密byte[] testEncrypted = SM2Util.encrypt(restoredPublicKey, data);byte[] testDecrypted = SM2Util.decrypt(restoredPrivateKey, testEncrypted);String testDecryptedText = new String(testDecrypted, "UTF-8");System.out.println("   密钥序列化验证: " + originalText.equals(testDecryptedText) + "\n");// 6. 签名验签测试System.out.println("6. 签名验签测试...");byte[] signature = SM2SignUtil.sign(privateKey, data);boolean verifyResult = SM2SignUtil.verify(publicKey, data, signature);System.out.println("   签名长度: " + signature.length + " 字节");System.out.println("   验签结果: " + verifyResult + "\n");// 7. SM3哈希测试System.out.println("7. SM3哈希测试...");byte[] sm3Hash = SM2SignUtil.sm3Hash(data);String sm3HashHex = SM2SignUtil.sm3HashHex(data);System.out.println("   SM3哈希长度: " + sm3Hash.length + " 字节");System.out.println("   SM3哈希值: " + sm3HashHex + "\n");System.out.println("=== 所有测试完成 ===\n");System.out.println("8. 生成x,y坐标");// 最简方式:直接在您的代码中使用BCECPublicKey sm2PublicKey = (BCECPublicKey) publicKey;BigInteger x = sm2PublicKey.getQ().getAffineXCoord().toBigInteger();BigInteger y = sm2PublicKey.getQ().getAffineYCoord().toBigInteger();// 如果需要十六进制String xHex = x.toString(16);String yHex = y.toString(16);// 补齐到64位(32字节)xHex = String.format("%64s", xHex).replace(' ', '0');yHex = String.format("%64s", yHex).replace(' ', '0');System.out.println("   x坐标: " + xHex);System.out.println("   y坐标: " + yHex);System.out.println("   公钥: " + SM2Util.getPublicKeyString(publicKey) + " 字节");} catch (Exception e) {System.err.println("执行过程中发生错误: " + e.getMessage());e.printStackTrace();}}
}

http://www.dtcms.com/a/490807.html

相关文章:

  • uniapp微信小程序+vue3基础内容介绍~(含标签、组件生命周期、页面生命周期、条件编译(一码多用)、分包))
  • 微信小程序报错 ubepected character `的style换行问题
  • H5封装打包小程序助手抖音快手微信小程序看广告流量主开源
  • 金华建设局网站做爰片在线看网站
  • 如何做二维码链接网站虚拟空间的网站赚钱吗
  • 营业部绩效考核方案与管理方法
  • 光刻刻蚀工艺控制要点及材料技术进展与限制
  • VPS SSH密钥登录配置指南:告别密码,拥抱安全
  • 注入“侨动力” 锻造“湘非链”
  • 做网站自己申请域名还是建站公司菏泽最好的网站建设公司
  • 网站建设方面书籍温州网站建设案例
  • 【Linux】Linux 零拷贝技术全景解读:从内核到硬件的性能优化之道
  • 微软ML.NET技术详解:从数据科学到生产部署的全栈解决方案
  • 镇江网站搜索引擎优化做外贸雨伞到什么网站
  • 网站收录一般多久沈阳建设学院
  • C++ AI 编程助手
  • 编程之python基础
  • 【系统分析师】写作框架:软件设计模式及其应用
  • leetcode 2598 执行操作后最大MEX
  • GPTBots Multi-Agent架构解析:如何通过多Agent协同实现业务智能化升级
  • 深圳网站建设智能小程序礼品网站如何做
  • 预约洗车小程序
  • 四字母域名建设网站可以吗乐清房产在线网
  • 中后台管理系统导航布局切换的技术原理解析
  • 【Android 、Java】为什么HashMap在JDK8中要将链表转换为红黑树的阈值设为8?这个数字是如何确定的?
  • Django中处理多数据库场景
  • 建设信源网站全国分类信息网站排名
  • MathType延时使用
  • Vue3 基础语法全解析:从入门到实战的核心指南
  • 莆田建站服务相馆网站建设费用预算