实战:Java基础系列之【国密SM:SM2、SM3、SM4的java实现bcprov-jdk】

概叙

科普文: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方法的问题。这通常是由于新版本库中该方法已被移除或更名所导致的。

为了解决这个问题,我们需要采取以下步骤:

  1. 确认问题原因:首先,我们需要确定问题的具体原因。可能是因为bcprov-jdk15on包的版本升级导致了Hex类中decodeStrict方法的移除或更名。也可能是其他依赖库的版本不匹配所引起的。
  2. 查找替代方法:一旦确认问题原因,我们需要查找替代decodeStrict方法的可用方法。在bcprov-jdk15on包的文档或源代码中查找类似功能的替代方法。如果新版本中已经有了新的方法,确保你的代码已经正确地引用了新的方法。
  3. 修改代码:根据替代方法的要求,修改代码中调用decodeStrict方法的部分。这可能涉及到替换方法名称、修改参数类型或值等。确保修改后的代码能够正确地解析和转换Hex编码的数据。
  4. 测试修改:完成代码修改后,进行充分的测试以确保修改不会引入新的问题。测试应覆盖所有受影响的场景,包括正常情况和异常情况。确保修改后的代码在各种情况下都能正常工作。
  5. 部署修复:将修改后的代码部署到服务器上,并监控系统的运行状况。如果一切正常,则说明问题已得到解决。如果仍然存在问题,请根据测试结果进一步排查和修复问题。

通过以上步骤,我们可以解决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

使用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();
        }

    }
}

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

01Byte空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值