概叙
科普文:Java基础系列之【java加密/解密、签名】-CSDN博客
科普文:Java基础系列之【java加密/解密、签名之国密SM:SM1(SCB2)、SM2、SM3、SM4、SM7、SM9,ZUC】-CSDN博客
前面详细介绍了国密SM:SM1(SCB2)、SM2、SM3、SM4、SM7、SM9,ZUC。 这里我们详细看一下SM2、SM3、SM4的java实现bcprov-jdk。
SM2(非对称加密)与其它加密算法比较
国际上通用的非对称加密算法有RSA、D-H算法;SM2属于椭圆曲线加密算法(ECC)。
SM2加密和解密是不同的秘钥,需要提前生成一对公钥和私钥,公钥用来加密,私钥用来解密
SM3(hash算法)与其它hash算法比较
国际上通用的hash算法为SHA系列算法,MD4 MD5算法。
SM3实在SHA-256基础上改进的算法。SM3是单向加密,是无法解密的;SM2、SM4都是可以加解密的。
SM4(对称)与其它算法的比较
国际上通过的对称加密算法是DES/AES算法。SM4在进行分组加密的时候进行了非线性变换。
SM4的加解密用的是一样的秘钥。
java国密库:bcprov-jdk
Documentation Bouncy Castle Java - Bouncycastle
bcprov-jdk各版本的主要区别在于它们支持的Java Development Kit 版本不同,以及可能包含的功能更新和安全性改进。
具体来说:
- 1. JDK版本兼容性:例如,bcprov-jdk15on是专为JDK 1.5及以上版本设计的,而像bcprov-jdk16这样的版本则可能特别针对JDK 1.6进行优化。随着Java平台的更新,这些库也会相应地进行调整,以确保与新版本的JDK保持兼容。
- 2. 功能更新:随着版本的迭代,bcprov-jdk可能会加入新的加密算法、优化现有算法的性能,或者增强安全性。例如,较新的版本可能支持更多的密码学标准或提供了改进后的API接口,使得开发者能够更方便地集成和使用这些功能。
- 3. 安全性改进:安全是密码学库的核心关注点。因此,新版本的bcprov-jdk可能会包含对已知安全漏洞的修复,或者引入了新的安全措施来防范潜在的安全威胁。这些改进有助于保护使用这些库的应用程序免受攻击。
举例来说,bcprov-jdk15on-1.69.jar是一个具体的版本,它提供了强密码学的支持,包括多种加密算法如AES、DES、RSA和DSA等,以及数字签名的工具和方法。这个版本被广泛用于需要处理安全相关流程和数据的金融、保险、电子商务和网络安全应用中,以提高应用程序的安全性和可靠性。
总的来说,bcprov-jdk的不同版本主要区别在于JDK版本兼容性、功能更新以及安全性改进。
开发者在选择使用哪个版本时,应根据自己的需求来进行决策。
同时,为了保持应用程序的安全性,建议定期关注并更新到最新的bcprov-jdk版本。
版本冲突汇总
bcprov-jdk15to18和其他的bcprov版本jar包冲突(不同版本jar兼容)解决,与东方通bcprov-jdk15on.jar冲突解决
bcprov-jdk15to18和其他的bcprov版本jar包冲突(不同版本jar兼容)解决,与东方通bcprov-jdk15on.jar冲突解决-CSDN博客
bcprov-jdk16包冲突问题(不同版本jar兼容) 以及 maven-shade-plugin的使用
【复盘】bcprov-jdk16包冲突问题(不同版本jar兼容) 以及 maven-shade-plugin的使用_bcprov-jdk16-1.46.jar-CSDN博客
Exception in thread "main" java.lang.IllegalAccessError: tried to access method
org.bouncycastle.math.ec.ECPoint$Fp.<init>(Lorg/bouncycastle/math/ec/ECCurve;Lorg/bouncycastle/math/ec/ECFieldElement;
Lorg/bouncycastle/math/ec/ECFieldElement;)V from class SM2Utils.SM2 at SM2Utils.
SM2.<init>(SM2.java:51) at SM2Utils.SM2.Instance(SM2.java:36) at
SM2Utils.SM2SignUtils.sign(SM2SignUtils.java:120) at EncryptUtils.EncryptUtils.encryptText(EncryptUtils.java:22) at
总结:
1.遇到问题不能慌张,需要慢慢分析出问题的来龙去脉,找到具体的原因。
2.jar冲突的问题,a.我们可以直接使用maven-helper进行直接jar冲突解决,但是对于高低版本不兼容的情况下,需要使用maven的插件进行解决。
具体的使用,https://maven.apache.org/plugins/maven-shade-plugin/shade-mojo.html
https://blog.csdn.net/yangguosb/article/details/80619481
解决bcprov-jdk15on包升级后服务器部署Hex类找不到decodeStrict方法问题
在升级bcprov-jdk15on包后,服务器部署时出现了Hex类找不到decodeStrict方法的问题。这通常是由于新版本库中该方法已被移除或更名所导致的。
为了解决这个问题,我们需要采取以下步骤:
- 确认问题原因:首先,我们需要确定问题的具体原因。可能是因为bcprov-jdk15on包的版本升级导致了Hex类中decodeStrict方法的移除或更名。也可能是其他依赖库的版本不匹配所引起的。
- 查找替代方法:一旦确认问题原因,我们需要查找替代decodeStrict方法的可用方法。在bcprov-jdk15on包的文档或源代码中查找类似功能的替代方法。如果新版本中已经有了新的方法,确保你的代码已经正确地引用了新的方法。
- 修改代码:根据替代方法的要求,修改代码中调用decodeStrict方法的部分。这可能涉及到替换方法名称、修改参数类型或值等。确保修改后的代码能够正确地解析和转换Hex编码的数据。
- 测试修改:完成代码修改后,进行充分的测试以确保修改不会引入新的问题。测试应覆盖所有受影响的场景,包括正常情况和异常情况。确保修改后的代码在各种情况下都能正常工作。
- 部署修复:将修改后的代码部署到服务器上,并监控系统的运行状况。如果一切正常,则说明问题已得到解决。如果仍然存在问题,请根据测试结果进一步排查和修复问题。
通过以上步骤,我们可以解决bcprov-jdk15on包升级后服务器部署Hex类找不到decodeStrict方法的问题。在未来的版本升级中,建议提前查看相关文档和更新日志,了解库中方法的变更情况,以便及时应对类似的问题。同时,保持对依赖库的关注和更新,以确保代码的稳定性和兼容性。
bcprov-jdk14、15on、16冲突,导致的签名不匹配。GeneralDigest signer information does not match
bcprov-jdk14、15on、16冲突,导致的签名不匹配。GeneralDigest signer information does not match - Huathy - 博客园
组件风险分析与修复指引:Bcprov Jdk15on 组件安全风险
组件风险分析与修复指引:Bcprov Jdk15on 组件安全风险 - FreeBuf网络安全行业门户
针对开源组件得安全分析,遇到组件安全风险,可通过升级安全版本或打补丁的方式进行修复,升级组件版本常遇安全兼容性的问题,可通过修复代码中的风险点进行打补丁修复。
如下,是Bcprov Jdk15on 1.70版本 组件安全风险分析:
依赖坐标:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</<artifactId>> <version>1.70</version> </dependency>
漏洞风险——Bouncy Castle 信任管理问题漏洞
CNNVD-202307-168
CVE-2023-33201
漏洞描述
Bouncy Castle是Bouncy Castle组织的密码学中使用的API集合。它包括适用于Java和C#编程语言的API 。
Bouncy Castle For Java 1.74之前版本存在安全漏洞,该漏洞源于在将证书的Subject Name插入到搜索过滤器时没有进行转义,导致存LDAP注入漏洞。
解决方案
相关commit:
漏洞补丁:
参考方案:
- 目前厂商已发布升级补丁以修复漏洞,补丁获取链接:
added filter encode to search · bcgit/bc-java@e8c409a · GitHub
安全建议
Home · bcgit/bc-java Wiki · GitHub
例外修复分析
经分析,漏洞风险涉及Java类如下:
org.ouncycastle.jce.provider.X509LDAPCertStoreApi
参照官方的目前厂商已发布升级补丁以修复漏洞,补丁获取链接:
added filter encode to search · bcgit/bc-java@e8c409a · GitHub
得以了解,Github上面就是对search方法的第二个参数进行了参数过滤:
安全例外修复代码如下(打补丁)
然后我们在项目路径下打个补丁,JVM的加载机制会决定,新写的这个类,会把jar包里面的那个同名同路径的覆盖掉。如下:
[X509LDAPCertStoreSpi.java]
package org.bouncycastle.jce.provider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.cert.CRL;
import java.security.cert.CRLSelector;
import java.security.cert.CertSelector;
import java.security.cert.CertStoreException;
import java.security.cert.CertStoreParameters;
import java.security.cert.CertStoreSpi;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRLSelector;
import java.security.cert.X509CertSelector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.x509.CertificatePair;
import org.bouncycastle.jce.X509LDAPCertStoreParameters;
public class X509LDAPCertStoreSpi extends CertStoreSpi {
private X509LDAPCertStoreParameters params;
private static String LDAP_PROVIDER = "com.sun.jndi.ldap.LdapCtxFactory";
private static String REFERRALS_IGNORE = "ignore";
private static final String SEARCH_SECURITY_LEVEL = "none";
private static final String URL_CONTEXT_PREFIX = "com.sun.jndi.url";
public X509LDAPCertStoreSpi(CertStoreParameters var1) throws InvalidAlgorithmParameterException {
super(var1);
if (!(var1 instanceof X509LDAPCertStoreParameters)) {
throw new InvalidAlgorithmParameterException(X509LDAPCertStoreSpi.class.getName() + ": parameter must be a " + X509LDAPCertStoreParameters.class.getName() + " object\n" + var1.toString());
} else {
this.params = (X509LDAPCertStoreParameters)var1;
}
}
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static {
// Filter encoding table -------------------------------------
// fill with char itself
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
}
// escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00";
}
/**
* Escape a value for use in a filter.
* @param value the value to escape.
* @return a properly escaped representation of the supplied value.
*/
private String filterEncode(String value)
{
if (value == null)
{
return null;
}
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
}
else {
// default: add the char
encodedValue.append(c);
}
}
return encodedValue.toString();
}
private DirContext connectLDAP() throws NamingException {
Properties var1 = new Properties();
var1.setProperty("java.naming.factory.initial", LDAP_PROVIDER);
var1.setProperty("java.naming.batchsize", "0");
var1.setProperty("java.naming.provider.url", this.params.getLdapURL());
var1.setProperty("java.naming.factory.url.pkgs", "com.sun.jndi.url");
var1.setProperty("java.naming.referral", REFERRALS_IGNORE);
var1.setProperty("java.naming.security.authentication", "none");
InitialDirContext var2 = new InitialDirContext(var1);
return var2;
}
private String parseDN(String var1, String var2) {
int var4 = var1.toLowerCase().indexOf(var2.toLowerCase());
String var3 = var1.substring(var4 + var2.length());
int var5 = var3.indexOf(44);
if (var5 == -1) {
var5 = var3.length();
}
while(var3.charAt(var5 - 1) == '\\') {
var5 = var3.indexOf(44, var5 + 1);
if (var5 == -1) {
var5 = var3.length();
}
}
var3 = var3.substring(0, var5);
var4 = var3.indexOf(61);
var3 = var3.substring(var4 + 1);
if (var3.charAt(0) == ' ') {
var3 = var3.substring(1);
}
if (var3.startsWith("\"")) {
var3 = var3.substring(1);
}
if (var3.endsWith("\"")) {
var3 = var3.substring(0, var3.length() - 1);
}
return var3;
}
public Collection engineGetCertificates(CertSelector var1) throws CertStoreException {
if (!(var1 instanceof X509CertSelector)) {
throw new CertStoreException("selector is not a X509CertSelector");
} else {
X509CertSelector var2 = (X509CertSelector)var1;
HashSet var3 = new HashSet();
Set var4 = this.getEndCertificates(var2);
var4.addAll(this.getCACertificates(var2));
var4.addAll(this.getCrossCertificates(var2));
Iterator var5 = var4.iterator();
try {
CertificateFactory var6 = CertificateFactory.getInstance("X.509", "BC");
while(true) {
byte[] var7;
do {
do {
if (!var5.hasNext()) {
return var3;
}
var7 = (byte[])((byte[])var5.next());
} while(var7 == null);
} while(var7.length == 0);
ArrayList var8 = new ArrayList();
var8.add(var7);
try {
CertificatePair var9 = CertificatePair.getInstance((new ASN1InputStream(var7)).readObject());
var8.clear();
if (var9.getForward() != null) {
var8.add(var9.getForward().getEncoded());
}
if (var9.getReverse() != null) {
var8.add(var9.getReverse().getEncoded());
}
} catch (IOException var13) {
} catch (IllegalArgumentException var14) {
}
Iterator var16 = var8.iterator();
while(var16.hasNext()) {
ByteArrayInputStream var10 = new ByteArrayInputStream((byte[])((byte[])var16.next()));
try {
Certificate var11 = var6.generateCertificate(var10);
if (var2.match(var11)) {
var3.add(var11);
}
} catch (Exception var12) {
}
}
}
} catch (Exception var15) {
throw new CertStoreException("certificate cannot be constructed from LDAP result: " + var15);
}
}
}
private Set certSubjectSerialSearch(X509CertSelector var1, String[] var2, String var3, String var4) throws CertStoreException {
HashSet var5 = new HashSet();
try {
if (var1.getSubjectAsBytes() == null && var1.getSubjectAsString() == null && var1.getCertificate() == null) {
var5.addAll(this.search(var3, "*", var2));
} else {
String var6 = null;
String var7 = null;
if (var1.getCertificate() != null) {
var6 = var1.getCertificate().getSubjectX500Principal().getName("RFC1779");
var7 = var1.getCertificate().getSerialNumber().toString();
} else if (var1.getSubjectAsBytes() != null) {
var6 = (new X500Principal(var1.getSubjectAsBytes())).getName("RFC1779");
} else {
var6 = var1.getSubjectAsString();
}
String var8 = this.parseDN(var6, var4);
var5.addAll(this.search(var3, "*" + var8 + "*", var2));
if (var7 != null && this.params.getSearchForSerialNumberIn() != null) {
var3 = this.params.getSearchForSerialNumberIn();
var5.addAll(this.search(var3, "*" + var7 + "*", var2));
}
}
return var5;
} catch (IOException var9) {
throw new CertStoreException("exception processing selector: " + var9);
}
}
private Set getEndCertificates(X509CertSelector var1) throws CertStoreException {
String[] var2 = new String[]{this.params.getUserCertificateAttribute()};
String var3 = this.params.getLdapUserCertificateAttributeName();
String var4 = this.params.getUserCertificateSubjectAttributeName();
Set var5 = this.certSubjectSerialSearch(var1, var2, var3, var4);
return var5;
}
private Set getCACertificates(X509CertSelector var1) throws CertStoreException {
String[] var2 = new String[]{this.params.getCACertificateAttribute()};
String var3 = this.params.getLdapCACertificateAttributeName();
String var4 = this.params.getCACertificateSubjectAttributeName();
Set var5 = this.certSubjectSerialSearch(var1, var2, var3, var4);
if (var5.isEmpty()) {
var5.addAll(this.search((String)null, "*", var2));
}
return var5;
}
private Set getCrossCertificates(X509CertSelector var1) throws CertStoreException {
String[] var2 = new String[]{this.params.getCrossCertificateAttribute()};
String var3 = this.params.getLdapCrossCertificateAttributeName();
String var4 = this.params.getCrossCertificateSubjectAttributeName();
Set var5 = this.certSubjectSerialSearch(var1, var2, var3, var4);
if (var5.isEmpty()) {
var5.addAll(this.search((String)null, "*", var2));
}
return var5;
}
public Collection engineGetCRLs(CRLSelector var1) throws CertStoreException {
String[] var2 = new String[]{this.params.getCertificateRevocationListAttribute()};
if (!(var1 instanceof X509CRLSelector)) {
throw new CertStoreException("selector is not a X509CRLSelector");
} else {
X509CRLSelector var3 = (X509CRLSelector)var1;
HashSet var4 = new HashSet();
String var5 = this.params.getLdapCertificateRevocationListAttributeName();
HashSet var6 = new HashSet();
Iterator var7;
String var9;
if (var3.getIssuerNames() != null) {
for(var7 = var3.getIssuerNames().iterator(); var7.hasNext(); var6.addAll(this.search(var5, "*" + var9 + "*", var2))) {
Object var8 = var7.next();
var9 = null;
String var10;
if (var8 instanceof String) {
var10 = this.params.getCertificateRevocationListIssuerAttributeName();
var9 = this.parseDN((String)var8, var10);
} else {
var10 = this.params.getCertificateRevocationListIssuerAttributeName();
var9 = this.parseDN((new X500Principal((byte[])((byte[])var8))).getName("RFC1779"), var10);
}
}
} else {
var6.addAll(this.search(var5, "*", var2));
}
var6.addAll(this.search((String)null, "*", var2));
var7 = var6.iterator();
try {
CertificateFactory var13 = CertificateFactory.getInstance("X.509", "BC");
while(var7.hasNext()) {
CRL var12 = var13.generateCRL(new ByteArrayInputStream((byte[])((byte[])var7.next())));
if (var3.match(var12)) {
var4.add(var12);
}
}
return var4;
} catch (Exception var11) {
throw new CertStoreException("CRL cannot be constructed from LDAP result " + var11);
}
}
}
private Set search(String var1, String var2, String[] var3) throws CertStoreException {
var2 = filterEncode(var2);
String var4 = var1 + "=" + var2;
if (var1 == null) {
var4 = null;
}
DirContext var5 = null;
HashSet var6 = new HashSet();
try {
var5 = this.connectLDAP();
SearchControls var7 = new SearchControls();
var7.setSearchScope(2);
var7.setCountLimit(0L);
for(int var8 = 0; var8 < var3.length; ++var8) {
String[] var9 = new String[]{var3[var8]};
var7.setReturningAttributes(var9);
String var10 = "(&(" + var4 + ")(" + var9[0] + "=*))";
if (var4 == null) {
var10 = "(" + var9[0] + "=*)";
}
NamingEnumeration var11 = var5.search(this.params.getBaseDN(), var10, var7);
while(var11.hasMoreElements()) {
SearchResult var12 = (SearchResult)var11.next();
NamingEnumeration var13 = ((Attribute)var12.getAttributes().getAll().next()).getAll();
while(var13.hasMore()) {
Object var14 = var13.next();
var6.add(var14);
}
}
}
} catch (Exception var22) {
throw new CertStoreException("Error getting results from LDAP directory " + var22);
} finally {
try {
if (null != var5) {
var5.close();
}
} catch (Exception var21) {
}
}
return var6;
}
}
使用bouncycastle 报错 bcprov-jdk15on-1.64.jar has unsigned / JCE cannot authenticate the provider BC
JDK版本 1.7
bouncycastle 包:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.64</version>
</dependency>
报错堆栈信息如下:
java.security.NoSuchProviderException: JCE cannot authenticate the provider BC
at javax.crypto.JceSecurity.getInstance(JceSecurity.java:101)
at javax.crypto.KeyGenerator.getInstance(KeyGenerator.java:265)
......
Caused by: java.util.jar.JarException: file:/C:/Users/xxxx/.m2/repository/org/bouncycastle/bcprov-jdk15on/1.64/bcprov-jdk15on-1.64.jar has unsigned entries - org/bouncycastle/LICENSE.class
at javax.crypto.JarVerifier.verifySingleJar(JarVerifier.java:462)
at javax.crypto.JarVerifier.verifyJars(JarVerifier.java:322)
at javax.crypto.JarVerifier.verify(JarVerifier.java:250)
at javax.crypto.JceSecurity.verifyProviderJar(JceSecurity.java:161)
at javax.crypto.JceSecurity.getVerificationResult(JceSecurity.java:187)
at javax.crypto.JceSecurity.getInstance(JceSecurity.java:98)
报错加载jar包时签名验证不通过,导致NoSuchProviderException。
如果jdk版本换成1.8就可以正常加载运行了。
官方的解释如下图:
所以如果你不是使用jdk1.8版本的话,可以使用这个包:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.64</version>
</dependency>
SM2、SM3、SM4的java实现bcprov-jdk
下面代码,本地验证有效。
依赖包
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.77</version>
</dependency>
SM2
package com.zxx.study.algorithm.datastruct.Algorithm.sm.sm2344;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
/**
* @author zhouxx
* @create 2024-12-04 15:55
*/
public class SM2Utils {
public static KeyPair createECKeyPair() {
final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
// 获取一个椭圆曲线类型的密钥对生成器
final KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
kpg.initialize(sm2Spec, new SecureRandom());
return kpg.generateKeyPair();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String encrypt(String publicKeyHex, String data) {
return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, 1);
}
public static String encrypt(BCECPublicKey publicKey, String data, int modeType) {
//加密模式
SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
if (modeType != 1) {
mode = SM2Engine.Mode.C1C2C3;
}
ECParameterSpec ecParameterSpec = publicKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
SM2Engine sm2Engine = new SM2Engine(mode);
sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
byte[] arrayOfBytes = null;
try {
byte[] in = data.getBytes("utf-8");
arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
} catch (Exception e) {
System.out.println("SM2加密时出现异常:" + e.getMessage());
e.printStackTrace();
}
return Hex.toHexString(arrayOfBytes);
}
public static String decrypt(String privateKeyHex, String cipherData) {
return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1);
}
public static String decrypt(BCECPrivateKey privateKey, String cipherData, int modeType) {
//解密模式
SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
if (modeType != 1) {
mode = SM2Engine.Mode.C1C2C3;
}
byte[] cipherDataByte = Hex.decode(cipherData);
ECParameterSpec ecParameterSpec = privateKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
ecDomainParameters);
SM2Engine sm2Engine = new SM2Engine(mode);
sm2Engine.init(false, ecPrivateKeyParameters);
String result = null;
try {
byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
result = new String(arrayOfBytes, "utf-8");
} catch (Exception e) {
System.out.println("SM2解密时出现异常" + e.getMessage());
}
return result;
}
private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
private static ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
public static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
if (pubKeyHex.length() > 128) {
pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
}
String stringX = pubKeyHex.substring(0, 64);
String stringY = pubKeyHex.substring(stringX.length());
BigInteger x = new BigInteger(stringX, 16);
BigInteger y = new BigInteger(stringY, 16);
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);
return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
}
public static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
BigInteger d = new BigInteger(privateKeyHex, 16);
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecDomainParameters);
return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
}
public static void main(String[] args) {
String publicKeyHex = null;
String privateKeyHex = null;
KeyPair keyPair = createECKeyPair();
PublicKey publicKey = keyPair.getPublic();
if (publicKey instanceof BCECPublicKey) {
//获取65字节非压缩缩的十六进制公钥串(0x04)
publicKeyHex = Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
System.out.println("SM2公钥:" + publicKeyHex);
}
PrivateKey privateKey = keyPair.getPrivate();
if (privateKey instanceof BCECPrivateKey) {
//获取32字节十六进制私钥串
privateKeyHex = ((BCECPrivateKey) privateKey).getD().toString(16);
System.out.println("SM2私钥:" + privateKeyHex);
}
/**
* 公钥加密
*/
String data = "=========需要加密的数据=========";
//将十六进制公钥串转换为 BCECPublicKey 公钥对象
String encryptData = encrypt(publicKeyHex, data);
System.out.println("加密结果:" + encryptData);
/**
* 私钥解密
*/
//将十六进制私钥串转换为 BCECPrivateKey 私钥对象
String decryptData = decrypt(privateKeyHex, encryptData);
System.out.println("是否一致:"+decryptData.equals(data)+"; 解密结果:" + decryptData);
/**
*
* SM2公钥:049e2f12c6941cc1d31c59d7faed730bb163e1c8ccf30bc0576046bcbc9c6b50c7895754c36fc83529414590edfd5ba266ab87c5698aba3c149e907bd180b95df2
* SM2私钥:2c8d2f9c402242a0ae8465600a3c4062809c967a7eabbc4aee5404e744d84756
* 加密结果:042c6cce193c34fd237b743507fdc5d63de950727afcaae6dc5567cf6712c464d1361e6d6fe44877898c68645c391c408b7a050bbd8b8c7910010da4da3793cef66b421496eb9981bc3235bbf2bfc5eb518bb22875ac92374f8b0635c8d583995613f06c259ae514839497691986740e4e6a531e84f7724401b6306896b5681c563d20ddb564aedb
* 是否一致:true; 解密结果:=========需要加密的数据=========
*
* */
}
}
运行结果:
SM3
package com.zxx.study.algorithm.datastruct.Algorithm.sm.sm2344;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.nio.charset.StandardCharsets;
import java.security.Security;
/**
* @author zhouxx
* @create 2024-12-04 16:13
*/
public class SM3Utils {
static {
// 添加安全提供者(SM2,SM3,SM4等加密算法,CBC、CFB等加密模式,PKCS7Padding等填充方式,不在Java标准库中,由BouncyCastleProvider实现)
Security.addProvider(new BouncyCastleProvider());
}
/**
* SM3,国家商用密码(Shang Mi3)也称国密3,是中华人民共和国政府采用的一种密码散列函数标准,由国家密码管理局于2010年12月17日发布。
* <p>
* 输入:待加密的字符串
* 输出:256位(16字节)或64个16进制字符(常用)
* 应用:密码管理、数字签名、文件完整性校验
* 安全性:★★☆☆☆
*
* @param plainString 明文
* @return cipherString 密文
*/
public static String sm3(String plainString) {
String cipherString = null;
try {
// 创建SM3Digest对象
SM3Digest sm3Digest = new SM3Digest();
// 初始化SM3计算
sm3Digest.update(plainString.getBytes(StandardCharsets.UTF_8), 0, plainString.length());
// 创建输出缓冲区
byte[] cipherBytes = new byte[sm3Digest.getDigestSize()];
// 计算SM3摘要
sm3Digest.doFinal(cipherBytes, 0);
// 输出16进制字符串
StringBuilder sb = new StringBuilder();
for (byte b : cipherBytes) {
sb.append(String.format("%02x", b));
}
cipherString = sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return cipherString;
}
public static void main(String[] args) {
String plainString = "hello world, hello java!";
String sm3 = SM3Utils.sm3(plainString);
System.out.println("sm3加密前: " + plainString);
System.out.println("sm3加密后: " + sm3);
/**
* sm3加密前: hello world, hello java!
* sm3加密后: 1ac1da65f9c28ccac9fa62303d3b325d7bc180bf29e354c70d1b6662939c05d6
* */
}
}
运行结果
SM4
package com.zxx.study.algorithm.datastruct.Algorithm.sm.sm2344;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* 简单说明:加密算法依赖了groupId:org.bouncycastle中的bcprov-jdk15to18,Bouncy Castle (bcprov-jdk15to18)
* 提供了JDK 1.5 to 1.8可使用的大量标准加密算法实现,其中包含了SM2,SM3,SM4。
* 在这个类库基础上实现了一个SM4Util加解密工具类。
* 注意: 此版本我在JDK1.8环境下,不同版本JDK需要找到匹配的依赖版本1.8及以上可以使用bcprov-jdk18on。
* Bouncy Castle同时也提供了bcutil-jdk15to18可以实现SM4加解密。
*
* @author zhouxx
* @create 2024-12-04 12:06
*/
public class Sm4Utils {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ENCODING = "UTF-8";
public static final String ALGORIGTHM_NAME = "SM4";
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS7Padding";
public static final int DEFAULT_KEY_SIZE = 128;
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, "BC");
Key sm4Key = new SecretKeySpec(key, ALGORIGTHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
public static byte[] generateKey(String keyString) throws Exception {
// Use SHA-256 to hash the string and then take first 128 bits (16 bytes)
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(keyString.getBytes(StandardCharsets.UTF_8));
byte[] key = new byte[16];
System.arraycopy(hash, 0, key, 0, 16);
return key;
}
public static String encryptEcb(String key, String paramStr, String charset) throws Exception {
String cipherText = "";
if (null != paramStr && !"".equals(paramStr)) {
byte[] keyData = generateKey(key);
charset = charset.trim();
if (charset.length() <= 0) {
charset = ENCODING;
}
byte[] srcData = paramStr.getBytes(charset);
byte[] cipherArray = encryptEcbPadding(keyData, srcData);
cipherText = ByteUtils.toHexString(cipherArray);
}
return cipherText;
}
public static byte[] encryptEcbPadding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher("SM4/ECB/PKCS7Padding", Cipher.ENCRYPT_MODE, key);
byte[] bs = cipher.doFinal(data);
return bs;
}
public static String decryptEcb(String key, String cipherText, String charset) throws Exception {
String decryptStr = "";
byte[] keyData = generateKey(key);
byte[] cipherData = ByteUtils.fromHexString(cipherText);
byte[] srcData = decryptEcbPadding(keyData, cipherData);
charset = charset.trim();
if (charset.length() <= 0) {
charset = ENCODING;
}
decryptStr = new String(srcData, charset);
return decryptStr;
}
public static byte[] decryptEcbPadding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher("SM4/ECB/PKCS7Padding", Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
public static void main(String[] args) {
try {
String data = "311111190001010001";
String key = "test";
String cipher = encryptEcb(key, data, ENCODING);
String decryptEcbData=decryptEcb(key, cipher, ENCODING);
System.out.println("SM4密文:"+cipher);
System.out.println("是否一致:"+decryptEcbData.equals(data)+"; 解密结果:" + decryptEcbData);
data = "311111190001010001=========需要加密的数据=========311111190001010001";
key = "test";
cipher = encryptEcb(key, data, ENCODING);
decryptEcbData=decryptEcb(key, cipher, ENCODING);
System.out.println("SM4密文:"+cipher);
System.out.println("是否一致:"+decryptEcbData.equals(data)+"; 解密结果:" + decryptEcbData);
/**
*
* SM4密文:cf4efc5e398005e96321791d63d8a5c719be6c02971f2ee8bd33e568a08974e8
* 是否一致:true; 解密结果:311111190001010001
* SM4密文:cf4efc5e398005e96321791d63d8a5c711ae42298dbf130d71b4c0b8c64144b6b46a7cd5fdef6a0d33dd60bee2a03c9e3cbee40d8ece2a3213b8d7a440581018c00486afc3031830711f54a79ff4e87c
* 是否一致:true; 解密结果:311111190001010001=========需要加密的数据=========311111190001010001
*
* */
} catch (Exception var5) {
var5.printStackTrace();
}
}
}
运行结果: