Java SM2加解密

SM2是国产的非对称加密算法,对其软件层面实现支持的库有很多,比如gmsslbouncycastle,这里主要记录bouncycastle的Java用法。

不同的库相互之间的加解密可能存在不兼容的情况

如果需要使用对方的公钥进行加密数据,最好双方都使用同一个软件实现库,不然可能出现无法解密的情况:)
比如userA使用gmssl库和userB提供的公钥进行SM2数据加密,但是userB使用bouncycastle库和其私钥进行解密,这个时候就可能出现Invalid point encoding 115 … 的异常。

Invalid point encoding 115   at Org.BouncyCastle.Math.EC.ECCurve.DecodePoint(Byte[] encoded) in /_/crypto/src/math/ec/ECCurve.cs:line 457\n   at Org.BouncyCastle.Crypto.Engines.SM2Engine.Decrypt(Byte[] input, Int32 inOff, Int32 inLen) in /_/crypto/src/crypto/engines/SM2Engine.cs:line 119\n

bouncycastle库依赖引入,这里使用的是maven

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk15to18</artifactId>
  <version>1.68</version>
</dependency>

main函数

Java
public static void main(String[] args) {
    KeyPair keyPair = createECKeyPair();
    String publicKeyHex = SM2Utils.getPublicKey(keyPair);
    String privateKeyHex = SM2Utils.getPrivateKey(keyPair);
    System.out.println("publicKeyHex: " + publicKeyHex);
    System.out.println("privateKeyHex: " + privateKeyHex);

    String plaintext = "Hello world";
    System.out.println("plaintext: " + plaintext);
    boolean base64Format = true;
 
    String ciphertext = encrypt(publicKeyHex, plaintext, SM2Engine.Mode.C1C3C2, base64Format);
    System.out.println("ciphertext:" + ciphertext);

    plaintext = decrypt(privateKeyHex, ciphertext, SM2Engine.Mode.C1C3C2, base64Format);
    System.out.println("plaintext::" + plaintext);
}
publicKeyHex: 0408ff70e111335ad0398957594eda236cacf8f81223408d225cd91aa7db87ae3691c57610877bc34dff1d51df9648e8cf857f4813feb022aa0805b3de3a4609c1
privateKeyHex: 4de85e77df52061129d1e7476f3f30cb8aa8f674cf132c6562aec99610b85b02
plaintext: Hello world
ciphertext:BBMPuN9RN+QenemBqw39eQg2LeU8PykkSbn1kEaEpJ5kZuIsr2sfFz6lNIsxdgWKtToLZkUoON4ywxJE8ELwOKBucdk79KEeY58urWMqlsUx3kyCkmXDK97oxWvHEFZZ09CfRTSzbcv/MN0X
plaintext::Hello world

SM2Utils.java

Java
package wang.runs.sm2BCLib;

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;
import java.util.Base64;
 
/**
 * @ClassName SM2Utils
 * @Description SM2
 */
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, SM2Engine.Mode mode, boolean base64Format) {
        return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, mode, base64Format);
    }
    
 
    public static String encrypt(BCECPublicKey publicKey, String data, SM2Engine.Mode mode, boolean base64Format){
        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 Exception:" + e.getMessage());
            e.printStackTrace();
        }
        
        if(base64Format) {
        	return Base64.getEncoder().encodeToString(arrayOfBytes);
        }
        return Hex.toHexString(arrayOfBytes);

    }
 
    public static String decrypt(String privateKeyHex, String cipherData, SM2Engine.Mode mode, boolean base64Format) {
        return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, mode, base64Format);
    }
 
    public static String decrypt(BCECPrivateKey privateKey, String cipherData, SM2Engine.Mode mode, boolean base64Format) {
        byte[] cipherDataByte = null;
        if(base64Format) {
        	cipherDataByte = Base64.getDecoder().decode(cipherData);
        }else {
        	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 Exception:" + 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);
    }
    
	private static String getPublicKey(KeyPair keyPair) {
		PublicKey publicKey = keyPair.getPublic();
		return Hex.toHexString(((BCECPublicKey) publicKey).getQ().getEncoded(false));
	}

	private static String getPrivateKey(KeyPair keyPair) {
		PrivateKey privateKey = keyPair.getPrivate();
		return (((BCECPrivateKey) privateKey).getD().toString(16));
	}
 
    public static void main(String[] args) {
        KeyPair keyPair = createECKeyPair();
        String publicKeyHex = SM2Utils.getPublicKey(keyPair);
        String privateKeyHex = SM2Utils.getPrivateKey(keyPair);
        System.out.println("publicKeyHex: " + publicKeyHex);
        System.out.println("privateKeyHex: " + privateKeyHex);

        String plaintext = "Hello world";
        System.out.println("plaintext: " + plaintext);
        boolean base64Format = true;
 
        String ciphertext = encrypt(publicKeyHex, plaintext, SM2Engine.Mode.C1C3C2, base64Format);
        System.out.println("ciphertext:" + ciphertext);

        plaintext = decrypt(privateKeyHex, ciphertext, SM2Engine.Mode.C1C3C2, base64Format);
        System.out.println("plaintext::" + plaintext);
    }

}

Reference
《国密SM2: 加解密实现 java代码完整示例》