TRX пре 1 година
родитељ
комит
b7e8271e95

+ 300 - 0
PaymentServer/src/main/java/com/zhongshu/payment/server/core/utils/SM2EngineExtend.java

@@ -0,0 +1,300 @@
+package com.zhongshu.payment.server.core.utils;
+
+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.params.*;
+import org.bouncycastle.math.ec.ECConstants;
+import org.bouncycastle.math.ec.ECFieldElement;
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.BigIntegers;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+/**
+ * @author TRX
+ * @date 2024/9/2
+ */
+public class SM2EngineExtend {
+    private final Digest digest;
+
+    /**
+     * 是否为加密模式
+     */
+    private boolean forEncryption;
+    private ECKeyParameters ecKey;
+    private ECDomainParameters ecParams;
+    private int curveLength;
+    private SecureRandom random;
+    /**
+     * 密文排序方式
+     */
+    private int cipherMode;
+
+    /**
+     * BC库默认排序方式-C1C2C3
+     */
+    public static int CIPHERMODE_BC = 0;
+    /**
+     * 国密标准排序方式-C1C3C2
+     */
+    public static int CIPHERMODE_NORM = 1;
+
+    public SM2EngineExtend() {
+        this(new SM3Digest());
+    }
+
+    public SM2EngineExtend(Digest digest) {
+        this.digest = digest;
+    }
+
+    /**
+     * 设置密文排序方式
+     *
+     * @param cipherMode
+     */
+    public void setCipherMode(int cipherMode) {
+        this.cipherMode = cipherMode;
+    }
+
+    /**
+     * 默认初始化方法,使用国密排序标准
+     *
+     * @param forEncryption - 是否以加密模式初始化
+     * @param param         - 曲线参数
+     */
+    public void init(boolean forEncryption, CipherParameters param) {
+        init(forEncryption, CIPHERMODE_NORM, param);
+    }
+
+    /**
+     * 默认初始化方法,使用国密排序标准
+     *
+     * @param forEncryption 是否以加密模式初始化
+     * @param cipherMode    加密数据排列模式:1-标准排序;0-BC默认排序
+     * @param param         曲线参数
+     */
+    public void init(boolean forEncryption, int cipherMode, CipherParameters param) {
+        this.forEncryption = forEncryption;
+        this.cipherMode = cipherMode;
+        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;
+    }
+
+    /**
+     * 加密或解密输入数据
+     *
+     * @param in
+     * @param inOff
+     * @param inLen
+     * @return
+     * @throws InvalidCipherTextException
+     */
+    public byte[] processBlock(byte[] in, int inOff, int inLen) throws InvalidCipherTextException {
+        if (forEncryption) {
+            // 加密
+            return encrypt(in, inOff, inLen);
+        } else {
+            return decrypt(in, inOff, inLen);
+        }
+    }
+
+    /**
+     * 加密实现,根据cipherMode输出指定排列的结果,默认按标准方式排列
+     *
+     * @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);
+
+        byte[] c1;
+        ECPoint kPB;
+        do {
+            BigInteger k = nextK();
+
+            ECPoint c1P = ecParams.getG().multiply(k).normalize();
+
+            c1 = c1P.getEncoded(false);
+
+            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);
+        if (cipherMode == CIPHERMODE_NORM) {
+            return Arrays.concatenate(c1, c3, c2);
+        }
+        return Arrays.concatenate(c1, c2, c3);
+    }
+
+    /**
+     * 解密实现,默认按标准排列方式解密,解密时解出c2部分原文并校验c3部分
+     *
+     * @param in
+     * @param inOff
+     * @param inLen
+     * @return
+     * @throws InvalidCipherTextException
+     */
+    private byte[] decrypt(byte[] in, int inOff, int inLen)
+            throws InvalidCipherTextException {
+        byte[] c1 = new byte[curveLength * 2 + 1];
+
+        System.arraycopy(in, inOff, c1, 0, c1.length);
+
+        ECPoint c1P = ecParams.getCurve().decodePoint(c1);
+
+        ECPoint s = c1P.multiply(ecParams.getH());
+        if (s.isInfinity()) {
+            throw new InvalidCipherTextException("[h]C1 at infinity");
+        }
+
+        c1P = c1P.multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize();
+
+        byte[] c2 = new byte[inLen - c1.length - digest.getDigestSize()];
+        if (cipherMode == CIPHERMODE_BC) {
+            System.arraycopy(in, inOff + c1.length, c2, 0, c2.length);
+        } else {
+            // C1 C3 C2
+            System.arraycopy(in, inOff + c1.length + digest.getDigestSize(), c2, 0, c2.length);
+        }
+
+        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;
+        // 检查密文输入值C3部分和由摘要生成的C3是否一致
+        if (cipherMode == CIPHERMODE_BC) {
+            for (int i = 0; i != c3.length; i++) {
+                check |= c3[i] ^ in[c1.length + c2.length + i];
+            }
+        } else {
+            for (int i = 0; i != c3.length; i++) {
+                check |= c3[i] ^ in[c1.length + i];
+            }
+        }
+
+        clearBlock(c1);
+        clearBlock(c3);
+
+        if (check != 0) {
+            clearBlock(c2);
+            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]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void kdf(Digest digest, ECPoint c1, byte[] encData) {
+        int ct = 1;
+        int v = digest.getDigestSize();
+
+        byte[] buf = new byte[digest.getDigestSize()];
+        int off = 0;
+
+        for (int i = 1; i <= ((encData.length + v - 1) / v); i++) {
+            addFieldElement(digest, c1.getAffineXCoord());
+            addFieldElement(digest, c1.getAffineYCoord());
+            digest.update((byte) (ct >> 24));
+            digest.update((byte) (ct >> 16));
+            digest.update((byte) (ct >> 8));
+            digest.update((byte) ct);
+
+            digest.doFinal(buf, 0);
+
+            if (off + buf.length < encData.length) {
+                xor(encData, buf, off, buf.length);
+            } else {
+                xor(encData, buf, off, encData.length - off);
+            }
+
+            off += buf.length;
+            ct++;
+        }
+    }
+
+    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 = new BigInteger(qBitLength, random);
+        }
+        while (k.equals(ECConstants.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);
+    }
+
+    /**
+     * clear possible sensitive data
+     */
+    private void clearBlock(
+            byte[] block) {
+        for (int i = 0; i != block.length; i++) {
+            block[i] = 0;
+        }
+    }
+}

+ 191 - 0
PaymentServer/src/main/java/com/zhongshu/payment/server/core/utils/Sm2Util.java

@@ -0,0 +1,191 @@
+package com.zhongshu.payment.server.core.utils;
+
+import org.aspectj.apache.bcel.classfile.Constant;
+import org.bouncycastle.asn1.gm.GMNamedCurves;
+import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
+import org.bouncycastle.crypto.params.*;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import java.math.BigInteger;
+import java.security.*;
+
+import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.util.encoders.Hex;
+
+/**
+ * @author TRX
+ * @date 2024/9/2
+ */
+public class Sm2Util {
+
+    private static final String gmName = "sm2p256v1";
+
+    /**
+     * 获取sm2密钥对
+     * BC库使用的公钥=64个字节+1个字节(04标志位),BC库使用的私钥=32个字节
+     * SM2秘钥的组成部分有 私钥D 、公钥X 、 公钥Y , 他们都可以用长度为64的16进制的HEX串表示,
+     * <br/>SM2公钥并不是直接由X+Y表示 , 而是额外添加了一个头,当启用压缩时:公钥=有头+公钥X ,即省略了公钥Y的部分
+     *
+     * @param compressed 是否压缩公钥(加密解密都使用BC库才能使用压缩)
+     * @return
+     */
+    public static String[] getSm2Keys(boolean compressed) {
+        //获取一条SM2曲线参数
+        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(gmName);
+        //构造domain参数
+        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
+        //1.创建密钥生成器
+        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
+        //2.初始化生成器,带上随机数
+        try {
+            keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
+        } catch (NoSuchAlgorithmException e) {
+            System.out.println(e.getMessage());
+        }
+        //3.生成密钥对
+        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
+        ECPublicKeyParameters publicKeyParameters = (ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic();
+        ECPoint ecPoint = publicKeyParameters.getQ();
+        // 把公钥放入map中,默认压缩公钥
+        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
+        String publicKey = Hex.toHexString(ecPoint.getEncoded(compressed));
+        ECPrivateKeyParameters privateKeyParameters = (ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate();
+        BigInteger intPrivateKey = privateKeyParameters.getD();
+        // 把私钥放入map中
+        String privateKey = intPrivateKey.toString(16);
+        String[] KeyPairOfString = new String[2];
+        KeyPairOfString[0] = publicKey;
+        KeyPairOfString[1] = privateKey;
+
+        return KeyPairOfString;
+    }
+
+    /**
+     * SM2加密算法
+     *
+     * @param publicKey 公钥
+     * @param data      待加密的数据
+     * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
+     */
+    public static String encrypt(String publicKey, String data) {
+        // 按国密排序标准加密
+        return encrypt(publicKey, data, 1);
+    }
+
+    /**
+     * SM2加密算法
+     *
+     * @param publicKey  公钥
+     * @param data       待加密的数据
+     * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
+     * @return 密文,BC库产生的密文带由04标识符,与非BC库对接时需要去掉开头的04
+     */
+    public static String encrypt(String publicKey, String data, int cipherMode) {
+        // 获取一条SM2曲线参数
+        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(gmName);
+        // 构造ECC算法参数,曲线方程、椭圆曲线G点、大整数N
+        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
+        //提取公钥点
+        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
+        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
+        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
+
+        SM2EngineExtend sm2Engine = new SM2EngineExtend();
+        // 设置sm2为加密模式
+        sm2Engine.init(true, cipherMode, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
+
+        byte[] arrayOfBytes = null;
+        try {
+            byte[] in = data.getBytes();
+            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+        return Hex.toHexString(arrayOfBytes);
+    }
+
+    /**
+     * SM2解密算法
+     *
+     * @param privateKey 私钥
+     * @param cipherData 密文数据
+     * @return
+     */
+    public static String decrypt(String privateKey, String cipherData) {
+        // // 按国密排序标准解密
+        return decrypt(privateKey, cipherData, SM2EngineExtend.CIPHERMODE_NORM);
+    }
+
+    /**
+     * SM2解密算法
+     *
+     * @param privateKey 私钥
+     * @param cipherData 密文数据
+     * @param cipherMode 密文排列方式0-C1C2C3;1-C1C3C2;
+     * @return
+     */
+    public static String decrypt(String privateKey, String cipherData, int cipherMode) {
+        // 使用BC库加解密时密文以04开头,传入的密文前面没有04则补上
+        if (!cipherData.startsWith("04")) {
+            cipherData = "04" + cipherData;
+        }
+        byte[] cipherDataByte = Hex.decode(cipherData);
+
+        //获取一条SM2曲线参数
+        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(gmName);
+        //构造domain参数
+        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
+
+        BigInteger privateKeyD = new BigInteger(privateKey, 16);
+        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
+
+        SM2EngineExtend sm2Engine = new SM2EngineExtend();
+        // 设置sm2为解密模式
+        sm2Engine.init(false, cipherMode, privateKeyParameters);
+
+        String result = "";
+        try {
+            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
+            return new String(arrayOfBytes);
+        } catch (Exception e) {
+            System.out.println(e.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * 私钥签名
+     *
+     * @param data       未加密的密码明文
+     * @param privateKey
+     * @return
+     * @throws Exception
+     */
+    public static byte[] signByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
+        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
+        sig.initSign(privateKey);
+        sig.update(data);
+        byte[] ret = sig.sign();
+        return ret;
+    }
+
+    /**
+     * 公钥验签
+     *
+     * @param data      未加密的密码明文
+     * @param publicKey
+     * @param signature 签名
+     * @return
+     * @throws Exception
+     */
+    public static boolean verifyByPublicKey(byte[] data, PublicKey publicKey, byte[] signature) throws Exception {
+        Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
+        sig.initVerify(publicKey);
+        sig.update(data);
+        boolean ret = sig.verify(signature);
+        return ret;
+    }
+}

+ 41 - 0
PaymentServer/src/main/java/com/zhongshu/payment/server/core/utils/TestSM2.java

@@ -0,0 +1,41 @@
+package com.zhongshu.payment.server.core.utils;
+
+import cn.hutool.json.JSONObject;
+
+/**
+ * @author TRX
+ * @date 2024/9/2
+ */
+public class TestSM2 {
+    public static void main(String[] args) {
+        String pwd = "123456";
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.set("pwd", pwd);
+        jsonObject.set("name", "zhongshu");
+        jsonObject.set("age", 18);
+        jsonObject.set("mid", "10037e6f803bc8ab01805fd07db4000d");
+        pwd = jsonObject.toString();
+
+        System.out.println("未加密密码:" + pwd);
+        String[] sm2Keys = Sm2Util.getSm2Keys(false);
+        String pubKey = sm2Keys[0];
+        System.out.println("公钥:" + pubKey);
+        System.out.println("公钥base64:" + AesUtils.base64Encode(pubKey));
+        String priKey = sm2Keys[1];
+        System.out.println("私钥:" + priKey);
+
+        // 国密加密
+        String encrypt = Sm2Util.encrypt(pubKey, pwd);
+        System.out.println("国密加密后密码:" + encrypt);
+
+        // 国密解密
+        String decrypt = Sm2Util.decrypt(priKey, encrypt);
+        System.out.println("国密解密后密码:" + decrypt);
+
+        // BC库加密
+        String encrypt1 = Sm2Util.encrypt(pubKey, pwd, 1);
+        System.out.println("BC加密:" + encrypt1);
+        String decrypt1 = Sm2Util.decrypt(priKey, encrypt1, 1);
+        System.out.println("BC解密:" + decrypt1);
+    }
+}