JAVA使用SM2算法生成密钥对加密解密加签验签

简介

SM2是非对称加密算法,一提非对称加密算法,第一想到的是RSA,没错,这个就是替代RSA的。它是基于椭圆曲线密码的公钥密码算法标准,其秘钥长度256bit,包含数字签名、密钥交换和公钥加密,用于替换RSA/DH/ECDSA/ECDH等国际算法。可以满足电子认证服务系统等应用需求,由国家密码管理局于2010年12月17号发布。SM2采用的是ECC 256位的一种,其安全强度比RSA 2048位高,且运算速度快于RSA。随着密码技术和计算技术的发展,目前常用的1024位RSA算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。SM2算法在安全性、性能上都具有优势。

用途

可以用于前后端传输数据加密解密。可以用于对数据加签验签,确保报文的安全性和完整性。比如,生成一套前端公私钥密钥对,生成一套后端服务器公私钥密钥对。前端把参数json字符串通过服务器公钥用sm2算法加密,服务器后端接收到请求后用服务器私钥解密,拿到原始参数,处理数据并生成响应数据,把响应数据用前端公钥加密,前端接收到响应加密后数据,用前端私钥解密,拿到响应json。这个过程是快速且安全的。(一般这个过程在网关上公共实现)

代码实现

引入依赖(maven引入bcprov-jdk15on jar包,截止发文,最新版本是1.70)

java 复制代码
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.70</version>
</dependency>

新建StandardSM2Engine实体类

java 复制代码
package com.zhaohy.app.utils;

import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine.Mode;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;

/**
 * 自定义SM2Engine类,对加密解密数据进行了ASN.1编码
 */
public class StandardSM2Engine {

    private final Digest digest;
    private final Mode mode;

    private boolean forEncryption;
    private ECKeyParameters ecKey;
    private ECDomainParameters ecParams;
    private int curveLength;
    private SecureRandom random;

    public StandardSM2Engine() {
        this(new SM3Digest());
    }

    public StandardSM2Engine(Mode mode) {
        this(new SM3Digest(), mode);
    }

    public StandardSM2Engine(Digest digest) {
        this(digest, Mode.C1C2C3);
    }

    public StandardSM2Engine(Digest digest, Mode mode) {
        if (mode == null) {
            throw new IllegalArgumentException("mode cannot be NULL");
        }
        this.digest = digest;
        this.mode = mode;
    }

    public void init(boolean forEncryption, CipherParameters param) {
        this.forEncryption = forEncryption;

        if (forEncryption) {
            ParametersWithRandom rParam = (ParametersWithRandom) param;

            ecKey = (ECKeyParameters) rParam.getParameters();
            ecParams = ecKey.getParameters();

            ECPoint s = ((ECPublicKeyParameters) ecKey).getQ().multiply(ecParams.getH());
            if (s.isInfinity()) {
                throw new IllegalArgumentException("invalid key: [h]Q at infinity");
            }

            random = rParam.getRandom();
        } else {
            ecKey = (ECKeyParameters) param;
            ecParams = ecKey.getParameters();
        }

        curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8;
    }

    public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
        if (forEncryption) {
            return encrypt(in, inOff, inLen);
        } else {
            return decrypt(in, inOff, inLen);
        }
    }

    public int getOutputSize(int inputLen) {
        return (1 + 2 * curveLength) + inputLen + digest.getDigestSize();
    }

    protected ECMultiplier createBasePointMultiplier() {
        return new FixedPointCombMultiplier();
    }

    /**
     * 加密
     * 
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    private byte[] encrypt(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
        byte[] c2 = new byte[inLen];

        System.arraycopy(in, inOff, c2, 0, c2.length);

        ECMultiplier multiplier = createBasePointMultiplier();

        ECPoint c1P;
        ECPoint kPB;
        do {
            BigInteger k = nextK();

            c1P = multiplier.multiply(ecParams.getG(), k).normalize();

            kPB = ((ECPublicKeyParameters) ecKey).getQ().multiply(k).normalize();

            kdf(digest, kPB, c2);
        } while (notEncrypted(c2, in, inOff));

        byte[] c3 = new byte[digest.getDigestSize()];

        addFieldElement(digest, kPB.getAffineXCoord());
        digest.update(in, inOff, inLen);
        addFieldElement(digest, kPB.getAffineYCoord());

        digest.doFinal(c3, 0);

        return convertToASN1(c1P, c2, c3);
    }

    /**
     * 解密
     * 
     * @param in
     * @param inOff
     * @param inLen
     * @return
     * @throws InvalidCipherTextException
     */
    private byte[] decrypt(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
        byte[] decryptData = new byte[inLen];
        System.arraycopy(in, inOff, decryptData, 0, decryptData.length);

        BigInteger x;
        BigInteger y;
        byte[] originC3;
        byte[] c2;
        ECPoint c1P;
        byte[] c1;
        try (ASN1InputStream aIn = new ASN1InputStream(decryptData)) {
            ASN1Sequence seq;
            try {
                seq = (ASN1Sequence) aIn.readObject();
            } catch (IOException e) {
                throw new InvalidCipherTextException();
            }
            x = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
            y = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
            c1P = ecParams.getCurve().validatePoint(x, y);
            c1 = c1P.getEncoded(false);
            if (mode == Mode.C1C3C2) {
                originC3 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
                c2 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();
            } else {
                c2 = ASN1OctetString.getInstance(seq.getObjectAt(2)).getOctets();
                originC3 = ASN1OctetString.getInstance(seq.getObjectAt(3)).getOctets();
            }
        } catch (IOException e) {
            throw new InvalidCipherTextException();
        }

        ECPoint s = c1P.multiply(ecParams.getH());
        if (s.isInfinity()) {
            throw new InvalidCipherTextException("[h]C1 at infinity");
        }

        c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();

        kdf(digest, c1P, c2);

        byte[] c3 = new byte[digest.getDigestSize()];

        addFieldElement(digest, c1P.getAffineXCoord());
        digest.update(c2, 0, c2.length);
        addFieldElement(digest, c1P.getAffineYCoord());

        digest.doFinal(c3, 0);

        int check = 0;
        for (int i = 0; i != c3.length; i++) {
            check |= c3[i] ^ originC3[i];
        }

        Arrays.fill(c1, (byte) 0);
        Arrays.fill(c3, (byte) 0);

        if (check != 0) {
            Arrays.fill(c2, (byte) 0);
            throw new InvalidCipherTextException("invalid cipher text");
        }

        return c2;
    }

    private boolean notEncrypted(byte[] encData, byte[] in, int inOff) {
        for (int i = 0; i != encData.length; i++) {
            if (encData[i] != in[inOff + i]) {
                return false;
            }
        }

        return true;
    }

    private void kdf(Digest digest, ECPoint c1, byte[] encData) {
        int digestSize = digest.getDigestSize();
        byte[] buf = new byte[Math.max(4, digestSize)];
        int off = 0;

        Memoable memo = null;
        Memoable copy = null;

        if (digest instanceof Memoable) {
            addFieldElement(digest, c1.getAffineXCoord());
            addFieldElement(digest, c1.getAffineYCoord());
            memo = (Memoable) digest;
            copy = memo.copy();
        }

        int ct = 0;

        while (off < encData.length) {
            if (memo != null) {
                memo.reset(copy);
            } else {
                addFieldElement(digest, c1.getAffineXCoord());
                addFieldElement(digest, c1.getAffineYCoord());
            }

            Pack.intToBigEndian(++ct, buf, 0);
            digest.update(buf, 0, 4);
            digest.doFinal(buf, 0);

            int xorLen = Math.min(digestSize, encData.length - off);
            xor(encData, buf, off, xorLen);
            off += xorLen;
        }
    }

    private void xor(byte[] data, byte[] kdfOut, int dOff, int dRemaining) {
        for (int i = 0; i != dRemaining; i++) {
            data[dOff + i] ^= kdfOut[i];
        }
    }

    private BigInteger nextK() {
        int qBitLength = ecParams.getN().bitLength();

        BigInteger k;
        do {
            k = BigIntegers.createRandomBigInteger(qBitLength, random);
        } while (k.equals(BigIntegers.ZERO) || k.compareTo(ecParams.getN()) >= 0);

        return k;
    }

    private void addFieldElement(Digest digest, ECFieldElement v) {
        byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger());

        digest.update(p, 0, p.length);
    }

    private byte[] convertToASN1(ECPoint c1P, byte[] c2, byte[] c3) {
        ASN1Integer x = new ASN1Integer(c1P.getXCoord().toBigInteger());
        ASN1Integer y = new ASN1Integer(c1P.getYCoord().toBigInteger());
        DEROctetString derDig = new DEROctetString(c3);
        DEROctetString derEnc = new DEROctetString(c2);
        ASN1EncodableVector v = new ASN1EncodableVector();
        switch (mode) {
        case C1C3C2:
            v.add(x);
            v.add(y);
            v.add(derDig);
            v.add(derEnc);
            break;
        default:
            v.add(x);
            v.add(y);
            v.add(derEnc);
            v.add(derDig);
        }
        DERSequence seq = new DERSequence(v);
        try {
            return seq.getEncoded();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

新建Sm2Utils工具类

java 复制代码
package com.zhaohy.app.utils;

import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 * SM2工具类
 */
public class Sm2Utils {
    
    /**
     * 加签
     * @param plainText
     * @return
     */
    public static String sign(String plainText, String privateKeyStr) {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        try {   
            // 获取椭圆曲线KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
            byte[] privateKeyData = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
            Signature rsaSignature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);
            rsaSignature.initSign(keyFactory.generatePrivate(privateKeySpec));
            rsaSignature.update(plainText.getBytes());
            byte[] signed = rsaSignature.sign();
            return Base64.getEncoder().encodeToString(signed);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 验签
     * @param plainText
     * @param signatureValue
     * @return
     */
    public static boolean verify(String plainText, String signatureValue, String publicKeyStr) {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        try {
            // 获取椭圆曲线KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
            byte[] publicKeyData = Base64.getDecoder().decode(publicKeyStr);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);
            // 初始化为验签状态
            Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), provider);
            signature.initVerify(keyFactory.generatePublic(publicKeySpec));
            signature.update(Hex.decodeHex(plainText.toCharArray()));
            return signature.verify(Hex.decodeHex(signatureValue.toCharArray()));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
            LogUtils.error("验签失败", e);
            return false;
        } catch (DecoderException e) {
            LogUtils.error("验签失败", e);
            return false;
        }
    }
    
    /**
     * 加密
     * @param plainText
     * @return
     */
    public static byte[] encrypt(String plainText, String publicKeyStr) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        try {   
            // 获取椭圆曲线KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            byte[] publicKeyData = Base64.getDecoder().decode(publicKeyStr);
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyData);
            PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
            CipherParameters publicKeyParamerters = ECUtil.generatePublicKeyParameter(publicKey);
            //数据加密
            StandardSM2Engine engine = new StandardSM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
            engine.init(true, new ParametersWithRandom(publicKeyParamerters));
            byte[] encryptData = engine.processBlock(plainText.getBytes(), 0, plainText.getBytes().length);
            return encryptData;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException 
                | InvalidKeyException | InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 解密
     * @param encryptedText
     * @return
     */
    public static String decrypt(byte[] encryptedData, String privateKeyStr) {
        Security.addProvider(new BouncyCastleProvider());
        try {   
            // 获取椭圆曲线KEY生成器
            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            byte[] privateKeyData = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
            PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
            CipherParameters privateKeyParamerters = ECUtil.generatePrivateKeyParameter(privateKey);
            //数据解密
            StandardSM2Engine engine = new StandardSM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
            engine.init(false, privateKeyParamerters);
            byte[] plainText = engine.processBlock(encryptedData, 0, encryptedData.length);
            return new String(plainText);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException 
                | InvalidKeyException | InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * SM2算法生成密钥对
     * @return 密钥对信息
     */
    public static KeyPair generateSm2KeyPair() {
        try {
            final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
            // 获取一个椭圆曲线类型的密钥对生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            SecureRandom random = new SecureRandom();
            // 使用SM2的算法区域初始化密钥生成器
            kpg.initialize(sm2Spec, random);
            // 获取密钥对
            KeyPair keyPair = kpg.generateKeyPair();
            return keyPair;
        } catch (Exception e) {
            LogUtils.error("generate sm2 key pair failed:{}", e.getMessage(), e);
            return null;
        }
    }
    
    public static void main(String[] args) throws Exception {
        KeyPair keyPair = generateSm2KeyPair();
        String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        String publicKey  = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        String data = "{\"daId\":\"123456\"}";
        String encryptedJsonStr =  Hex.encodeHexString(encrypt(data, publicKey)) + "";//16进制字符串
        String decryptedJsonStr = decrypt(Hex.decodeHex(encryptedJsonStr), privateKey);
        String sign = Hex.encodeHexString(Base64.getDecoder().decode(sign(data, privateKey)));
        boolean flag = verify(Hex.encodeHexString(data.getBytes()), sign, publicKey);
        System.out.println("base64后privateKey:" + privateKey);
        System.out.println("base64后publicKey:" + publicKey);
        System.out.println("加密前数据:" + data);
        System.out.println("公钥加密后16进制字符串:" + encryptedJsonStr);
        System.out.println("私钥解密后数据:" + decryptedJsonStr);
        System.out.println("私钥加签后数据(16进制):" + sign);
        System.out.println("公钥验签结果:" + flag);
        
    }

}

工具类里已经写好了调用测试main方法,运行结果如下:

base64后privateKey:MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgmze2hmNoi3XpeMPmLNIQBOC7+P1iMbwGMMAdQXko0VWgCgYIKoEcz1UBgi2hRANCAATyF9jHsCvFaLkR4DS+viX9CShZEXc7Nc1OueDaIpzZx/DRTRejSpTOcFmb0B9TsyYutnRWAx46nfkQ289NVXjg
base64后publicKey:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE8hfYx7ArxWi5EeA0vr4l/QkoWRF3OzXNTrng2iKc2cfw0U0Xo0qUznBZm9AfU7MmLrZ0VgMeOp35ENvPTVV44A==
加密前数据:{"daId":"123456"}
公钥加密后16进制字符串:307b022100f1ab8b4fc8755d2ab84bf530f15ab14f5250d060d52679fd38e822a85eeceb860221009eef85a579ef9e2f27f44461a89046b95f8c630773be7ced1f3f3f48057f4777042064d9b5fb396591e022791858e334fc40b1ee9c93412442d403385266a00dd78704117b30ed38afcb361f8176acbbfbabad71e9
私钥解密后数据:{"daId":"123456"}
私钥加签后数据(16进制):304402201505caa731d9b70987a6c19f40618593e9acd8cad880dc001e52ef4b48638e2302204bdb5c5dc881789e716de7360aa387e5e4caa9f9f2fd6d1fbce35f1227b34440
公钥验签结果:true

可以看到加密解密加签验签都是成功的。上面加密后生成的是byte[],转换成16进制字符串处理的,也可以用base64实现byte[]和字符串的转换处理,但是要保证对应加解密都是用base64实现byte[]和字符串的转换,对应的上就行。

相关推荐
Allen Bright3 分钟前
maven概述
java·maven
qystca6 分钟前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
编程重生之路6 分钟前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端
薯条不要番茄酱6 分钟前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
今天吃饺子11 分钟前
2024年SCI一区最新改进优化算法——四参数自适应生长优化器,MATLAB代码免费获取...
开发语言·算法·matlab
是阿建吖!12 分钟前
【优选算法】二分查找
c++·算法
努力进修15 分钟前
“探索Java List的无限可能:从基础到高级应用“
java·开发语言·list
politeboy16 分钟前
k8s启动springboot容器的时候,显示找不到application.yml文件
java·spring boot·kubernetes
王燕龙(大卫)16 分钟前
leetcode 数组中第k个最大元素
算法·leetcode
Daniel 大东1 小时前
BugJson因为json格式问题OOM怎么办
java·安全