国密(SM2、SM3、SM4)工具类

前言
国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。

SM1 为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。

SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。

SM3 加密不可逆算法。可以用MD5作为对比理解,但MD5已被破解,故安全性比MD5高。该算法已公开。校验结果为256位。

SM4 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。

一、SM2、SM3:

1、SM2是一种非对称加密,类似RSA,既需要使用公私钥对来进行加密;
SM2相比RSA:公私钥对更短的同时,安全性更高,加解密比RSA更快;
2、SM3是一种散列算法,类似MD5,散列后得到固定长度的密文

具体使用流程如下:1、获取原文str
(1.5、很多公司会把原文进行一次SM3散列(加密)一次得到固定长度的字符串作为新的原文)
2、对原文str使用私钥进行加密,得到密文sign
3、使用公钥、原文str、密文sign进行验证

在这里插入代码片

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Base64;

public class SmxUtils {

    /**
     * 以下为SM2推荐曲线参数
     */
    public static final SM2P256V1Curve CURVE = new SM2P256V1Curve();
    public final static BigInteger SM2_ECC_N = CURVE.getOrder();
    public final static BigInteger SM2_ECC_H = CURVE.getCofactor();
    public final static BigInteger SM2_ECC_GX = new BigInteger(
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16);
    public final static BigInteger SM2_ECC_GY = new BigInteger(
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16);
    public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY);
    public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT,
            SM2_ECC_N, SM2_ECC_H);
    /**
     * End: SM2推荐曲线参数
     */
    public static final String FLAG_BIT = "04";

    /**
     * 加签
     * @param signData   sm3散列的密文
     * @return
     */
    public static String sign(String privateKey, String signData) {
        byte[] sm2Cipher = Base64.getDecoder().decode(signData);
        ECPrivateKeyParameters priKeyParameters = new ECPrivateKeyParameters(new BigInteger(privateKey,16), DOMAIN_PARAMS);
        byte[] signByte = null;
        try {
            signByte = sign(priKeyParameters,sm2Cipher);
        } catch (CryptoException e) {
            System.out.println(e.getMessage());
        }
        return Base64.getEncoder().encodeToString(signByte);
    }
    private static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] srcData) throws CryptoException {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = new ParametersWithRandom(priKeyParameters, new SecureRandom());
        signer.init(true, param);
        signer.update(srcData, 0, srcData.length);
        return signer.generateSignature();
    }
    /**
     * 验签
     * @param srcData   sm3散列后的明文
     * @param sign    sm2签名
     */
    public static boolean verify(String publicKey, String srcData, String sign) {
        if(publicKey.startsWith(FLAG_BIT)){
            publicKey = publicKey.substring(2);
        }
        assert publicKey != null;
        String publicKeyX = publicKey.substring(0,publicKey.length()/2);
        String publicKeyY = publicKey.substring(publicKey.length()/2);
        byte[] srcDataByte = Base64.getDecoder().decode(srcData);
        byte[] signByte = Base64.getDecoder().decode(sign);
        ECPublicKeyParameters pubKeyParameters = createECPublicKeyParameters(publicKeyX, publicKeyY, CURVE, DOMAIN_PARAMS);
        return verify(pubKeyParameters, srcDataByte, signByte);
    }

    private static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] srcData, byte[] sign) {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = pubKeyParameters;
        signer.init(false, param);
        signer.update(srcData, 0, srcData.length);
        return signer.verifySignature(sign);
    }

    private static ECPublicKeyParameters createECPublicKeyParameters(String xHex, String yHex,
                                                                    ECCurve curve, ECDomainParameters domainParameters) {
        return createECPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
                curve, domainParameters);
    }
    private static ECPublicKeyParameters createECPublicKeyParameters(byte[] xBytes, byte[] yBytes,
                                                                    ECCurve curve, ECDomainParameters domainParameters) {
        final byte uncompressedFlag = 0x04;
        int curveLength = (domainParameters.getCurve().getFieldSize() + 7) / 8;
        xBytes = fixToCurveLengthBytes(curveLength, xBytes);
        yBytes = fixToCurveLengthBytes(curveLength, yBytes);
        byte[] encodedPubKey = new byte[1 + xBytes.length + yBytes.length];
        encodedPubKey[0] = uncompressedFlag;
        System.arraycopy(xBytes, 0, encodedPubKey, 1, xBytes.length);
        System.arraycopy(yBytes, 0, encodedPubKey, 1 + xBytes.length, yBytes.length);
        return new ECPublicKeyParameters(curve.decodePoint(encodedPubKey), domainParameters);
    }

    private static byte[] fixToCurveLengthBytes(int curveLength, byte[] src) {
        if (src.length == curveLength) {
            return src;
        }

        byte[] result = new byte[curveLength];
        if (src.length > curveLength) {
            System.arraycopy(src, src.length - result.length, result, 0, result.length);
        } else {
            System.arraycopy(src, 0, result, result.length - src.length, src.length);
        }
        return result;
    }
 /**
     * 生成ECC密钥对
     *
     * @return ECC密钥对
     */
    public static void generateKeyPairParameter() {
        SecureRandom random = new SecureRandom();
        AsymmetricCipherKeyPair keyPair = generateKeyPairParameter(DOMAIN_PARAMS, random);
        ECPrivateKeyParameters priKey = (ECPrivateKeyParameters) keyPair.getPrivate();
        ECPublicKeyParameters pubKey = (ECPublicKeyParameters) keyPair.getPublic();
        String privateKey = ByteUtils.toHexString(priKey.getD().toByteArray()).toUpperCase();
        String publicKey = ByteUtils.toHexString(pubKey.getQ().getEncoded(false)).toUpperCase();
        System.out.println(privateKey);
        System.out.println(publicKey);
    }

    public static AsymmetricCipherKeyPair generateKeyPairParameter(ECDomainParameters domainParameters,
                                                                   SecureRandom random) {
        ECKeyGenerationParameters keyGenerationParams = new ECKeyGenerationParameters(domainParameters,
                random);
        ECKeyPairGenerator keyGen = new ECKeyPairGenerator();
        keyGen.init(keyGenerationParams);
        return keyGen.generateKeyPair();
    }
        /**
     * sm3哈希
     * @param srcData
     * @return
     */
    public static String sm3Hash(byte[] srcData) {
        SM3Digest digest = new SM3Digest();
        digest.update(srcData, 0, srcData.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        return ByteUtils.toHexString(hash);
    }

}

接下来我们看看如何使用这个工具类

1、生成SM2公私钥对:

注:生成的公钥都以04开头,且比私钥长

public static void main(String[] args) {
    generateKeyPairParameter();
}

结果:

私钥:00994EAF2CFCD81798B33E3F28528B2171A84A830580E7A82B4BCACF55535EE3F9
公钥:04987C490335D2283B2128F9A29E57F63D89D1954060F12B82201D2C8A3E307406DA715360D842E62F38C978B8E05FC7CEC6F06FFB66856D253FD8713739F09ECF

2、进行加签验签

以下示例是搭配sm3散列(加密)的标准使用流程,如无需要,可以不对原文进行散列:

    public static void main(String[] args) {
        String privateKey = "00991AAA8FFB2D4A2FB233DBBA9C6955E89C040D23A19A5C28B9584361F84D9E80";
        String publicKey = "049F294C6D8DB6C5660012EF80C8EC4A5411AB2AC6AA64DD367027F4AEF0E6F97D43B362BDD56204BCDFC53C7151F9F23BFC8671AB088724C88F4F44A69080E9D6";
        System.out.println();
        System.out.println("私钥:" + privateKey);
        System.out.println("公钥:" + publicKey);
        System.out.println("私钥长度--------------" + privateKey.getBytes().length);
        System.out.println("公钥长度--------------" + publicKey.getBytes().length);

        String requestContent = "{\"outTradeNo\":\"jkjh20231228024545123456\",\"payWay\":\"cmpay\",\"scene\":\"wap\",\"buyerId\":\"\",\"totalAmount\":0.01,\"realAmount\":0.01,\"discountableAmount\":0,\"subject\":\"test\",\"productCode\":\"000001\",\"productName\":\"test\",\"productDesc\":\"test\",\"productUrl\":\"http://\",\"clientIp\":\"8.8.8.8\",\"wechatOpenId\":\"\",\"tradeDate\":\"20231228\",\"authCode\":\"\",\"hallAreaCode\":\"0001\",\"hallCode\":\"0002\",\"terminalCode\":\"sb001\",\"clerkCode\":\"0003\",\"hallWindowCode\":\"0004\",\"notifyUrl\":\"http://\",\"operatorId\":\"99900000002\",\"timeoutExpress\":\"30m\",\"pageNotifyUrl\":\"http://\",\"extra\":\"test\",\"appId\":\"\",\"bankAbbreviation\":\"ICBC\",\"bankCardType\":\"\",\"mobileNumber\":\"\",\"packageCode\":\"\",\"provinceCode\":\"\",\"packageDiscountAmount\":0.01,\"packagePeriod\":\"12\",\"packageLevel\":128,\"packageProductAmount\":0.01,\"creditChannel\":\"\",\"specifiedChannel\":\"\",\"rechargeIp\":\"8.8.8.8\",\"discountCode\":\"\",\"payChannelFlag\":\"\",\"promoParams\":\"\",\"subMerchant\":\"\",\"settlementDept\":\"\",\"settlementItem\":\"\",\"merchantChannelType\":\"\",\"merchantId\":\"7770000000096133\",\"method\":\"trade.payment\",\"format\":\"JSON\",\"signType\":\"SM2\",\"version\":\"1.0.0\"}";
        // 对原文进行一次sm3加密
        String sm3Sign = SmxUtils.sm3Hash(requestContent.getBytes());
        System.out.println("sm3加密后的值为:"+ sm3Sign);
        String sm2Sign = SmxUtils.sign(privateKey, sm3Sign);
        System.out.println("SM2签名后的值为:"+ sm2Sign);
        boolean verifyFlag= SmxUtils.verify(publicKey, sm3Sign, sm2Sign);
        System.out.println("SM2验签是否通过:"+verifyFlag);
    }

以下是结果:

私钥:00991AAA8FFB2D4A2FB233DBBA9C6955E89C040D23A19A5C28B9584361F84D9E80
公钥:049F294C6D8DB6C5660012EF80C8EC4A5411AB2AC6AA64DD367027F4AEF0E6F97D43B362BDD56204BCDFC53C7151F9F23BFC8671AB088724C88F4F44A69080E9D6
私钥长度--------------66
公钥长度--------------130
sm3加密后的值为:3c864cd3d85caef1fe1e4e9cb4eb4a374718f4fdfccd6c98ffe5ae7b94fe9f5b
SM2签名后的值为:MEUCIQDLssy7EUdcNcX6J/+0pU9Ky414g3EJ2Nu98td7yW9YpgIgF5SsjLfH70vq1IU8NSjPD3IQt3tgoNJrMBa54w3CTOg=
SM2验签是否通过:true

在真实使用过程中,己方使用己方的私钥进行加签,公钥给对方进行验签

二、SM4:

SM4是一种对称加密,即使用一个密钥,对一个原文进行加密,同时也可以用这个密钥进行解密;
这个密钥的生成方法网上有很多,16位英文、数字、某些特定字符组合而成
以下是工具类:

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;

/**
 * @date 2024-01-09 21:45
 * @Author Wu_cm
 * @Version 1.0
 */
public class Sm4Util {
    public static final String ALGORITHM_NAME = "SM4";
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";

    /**
     * 加密
     * 加密模式:SM4/ECB/PKCS5Padding
     *
     * @param data 需要加密的内容
     * @param key  加密密码
     * @return
     */
    public static String encrypt(String data, String key) {
        try {
            byte[] byteData = data.getBytes(StandardCharsets.UTF_8);
            byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
            byte[] plainText = encryptEcbPadding(byteKey, byteData);
            return new String(Base64.encodeBase64(plainText));
        } catch (Exception e) {
            System.out.println("加密失败");
        }
        return null;
    }

    /**
     * 解密
     * 密文加密模式:SM4/ECB/PKCS5Padding
     *
     * @param data 需要解密的密文
     * @param key  加密密码
     * @return
     */
    public static String decrypt(String data, String key) {
        try {
            byte[] byteData = Base64.decodeBase64(data);
            byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
            byte[] cipherText = decryptEcbPadding(byteKey, byteData);
            return new String(cipherText, StandardCharsets.UTF_8);
        } catch (Exception e) {
            System.out.println("解密失败");
        }
        return null;
    }

    public static byte[] encryptEcbPadding(byte[] key, byte[] data)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
            NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }
    public static byte[] decryptEcbPadding(byte[] key, byte[] cipherText)
            throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }
    private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key)
            throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
            InvalidKeyException {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key);
        return cipher;
    }
}

接下来看看如何使用:


    public static void main(String[] args) {
        String smxkey="0X163905EF10AC00";
        String privateEncrypt = encrypt("abcdefg", smxkey);
        String publicEncrypt = decrypt("zEQnadqzCLF4DR/VKB2+Uw==", smxkey);
        System.out.println("加密后的密文:" + privateEncrypt);
        System.out.println("解密后的明文:" + publicEncrypt);
    }

SM4加解密,输出结果:

加密后的密文:zEQnadqzCLF4DR/VKB2+Uw==
解密后的明文:abcdefg

总结:SM1依赖物理芯片,不公开使用(非民用)
SM2是一种非对称加密,比RSA快,比RSA安全
SM3是一种加密不可逆算法,类似MD5,比MD5安全
SM4是一种对称加密,使用一个密钥对原文加密得到密文,也可以用密钥对密文解密得到原文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值