diff --git a/base-buildpackage/pom.xml b/base-buildpackage/pom.xml index 718ed2d5..b1782b5e 100644 --- a/base-buildpackage/pom.xml +++ b/base-buildpackage/pom.xml @@ -20,6 +20,61 @@ base-webapp ${revision} + + + + org.bouncycastle + bcmail-jdk15on + 1.56 + + + + + org.bouncycastle + bcpkix-jdk15on + 1.57 + + + + org.bouncycastle + bcprov-jdk15on + 1.57 + + + + + commons-codec + commons-codec + 1.9 + + + + + org.apache.commons + commons-lang3 + 3.9 + + + + + com.xiaoleilu + hutool-all + 3.0.9 + + + + + com.squareup.okhttp3 + okhttp + 3.3.0 + + + + + com.squareup.okio + okio + 1.8.0 + diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SHA1.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SHA1.java new file mode 100644 index 00000000..f7c99a65 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SHA1.java @@ -0,0 +1,21 @@ +package com.hzya.frame.finance.utils.pufabank; + +import java.security.MessageDigest; + +import javax.xml.bind.DatatypeConverter; + +public class SHA1 { + + public static String digest(String content) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); + messageDigest.update(content.getBytes("UTF-8")); + byte[] digestBytes = messageDigest.digest(); + return DatatypeConverter.printBase64Binary(digestBytes); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Cipher.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Cipher.java new file mode 100644 index 00000000..b074d671 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Cipher.java @@ -0,0 +1,105 @@ +package com.hzya.frame.finance.utils.pufabank; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.math.ec.ECPoint; + +/** + * 国密加密类实现 + * + */ +public class SM2Cipher { + + private int ct; + + private ECPoint p2; + + private SM3Digest sm3keybase;// SM3摘要 + + private SM3Digest sm3c3; + + private byte key[]; + + private byte keyOff; + + public SM2Cipher() { + this.ct = 1; + this.key = new byte[32]; + this.keyOff = 0; + } + + /** + * 重置 + */ + private void Reset() { + this.sm3keybase = new SM3Digest(); + this.sm3c3 = new SM3Digest(); + + byte p[] = SM2Util.byteConvert32Bytes(p2.getXCoord().toBigInteger()); + this.sm3keybase.update(p, 0, p.length); + this.sm3c3.update(p, 0, p.length); + + p = SM2Util.byteConvert32Bytes(p2.getYCoord().toBigInteger()); + this.sm3keybase.update(p, 0, p.length); + this.ct = 1; + NextKey(); + } + + private void NextKey() { + SM3Digest sm3keycur = new SM3Digest(this.sm3keybase); + sm3keycur.update((byte) (ct >> 24 & 0xff)); + sm3keycur.update((byte) (ct >> 16 & 0xff)); + sm3keycur.update((byte) (ct >> 8 & 0xff)); + sm3keycur.update((byte) (ct & 0xff)); + sm3keycur.doFinal(key, 0); + this.keyOff = 0; + this.ct++; + } + + public ECPoint Init_enc(SM2Factory sm2, ECPoint userKey) { + AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair(); + ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate(); + ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic(); + BigInteger k = ecpriv.getD(); + ECPoint c1 = ecpub.getQ(); + this.p2 = userKey.multiply(k); + Reset(); + return c1; + } + + public void Encrypt(byte data[]) { + this.sm3c3.update(data, 0, data.length); + for (int i = 0; i < data.length; i++) { + if (keyOff == key.length) { + NextKey(); + } + data[i] ^= key[keyOff++]; + } + } + + public void Init_dec(BigInteger userD, ECPoint c1) { + this.p2 = c1.multiply(userD); + Reset(); + } + + public void Decrypt(byte data[]) { + for (int i = 0; i < data.length; i++) { + if (keyOff == key.length) { + NextKey(); + } + data[i] ^= key[keyOff++]; + } + + this.sm3c3.update(data, 0, data.length); + } + + public void Dofinal(byte c3[]) { + byte p[] = SM2Util.byteConvert32Bytes(p2.getYCoord().toBigInteger()); + this.sm3c3.update(p, 0, p.length); + this.sm3c3.doFinal(c3, 0); + Reset(); + } +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Cryptor.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Cryptor.java new file mode 100644 index 00000000..923d7ba1 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Cryptor.java @@ -0,0 +1,173 @@ +package com.hzya.frame.finance.utils.pufabank; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.Security; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; + +/** + * 报文加解密工具类 + * + */ +public class SM2Cryptor { + + static { + Security.addProvider(new BouncyCastleProvider()); + } + + // 算法名称 + public static final String ALGORITHM_NAME = "sm4"; + + // P5填充 + public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding"; + + /** + * SHA256加密 + * + * @param str + * @return + */ + public static String sha256(String str) { + MessageDigest messageDigest = null; + String enencdeStr = ""; + try { + messageDigest = MessageDigest.getInstance("SHA-256"); + byte[] hash = messageDigest.digest(str.getBytes("UTF-8")); + enencdeStr = Hex.encodeHexString(hash); + } catch (Exception e) { + e.printStackTrace(); + } + return enencdeStr; + } + + /** + * MD5加密 + * + * @param hash + * @return + */ + public static String md5(String hash) { + String md5Str = DigestUtils.md5Hex(hash); + return md5Str; + } + + /** + * + * sm3加密处理 + * + * @param data + * @return 2019年11月26日 + * + */ + public static String sm3(String data) { + String charset = "UTF-8"; + String sm3Data = ""; + try { + byte[] dataBytes = data.getBytes(charset); + byte[] hashBytes = hash(dataBytes); + sm3Data = ByteUtils.toHexString(hashBytes); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return sm3Data; + } + + /** + * + * 返回长度为32位的byte数组 生成对应的hash值 + * + * @param dataBytes + * @return 2019年10月28日 + * + */ + public static byte[] hash(byte[] dataBytes) { + SM3Digest digest = new SM3Digest(); + digest.update(dataBytes, 0, dataBytes.length); + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + return hash; + } + + /** + * 报文体加密 + * + * @param key + * @param data + * @return + */ + public static String encrypt(String key, String data) { + + String encrypted = ""; + + try { + String charset = "UTF-8"; + + String sha256Key = sha256(key); + String sm3Key = sm3(sha256Key); + String md5Key = md5(sm3Key); + + byte[] keyBytes = ByteUtils.fromHexString(md5Key); + byte[] dataBytes = data.getBytes(charset); + + Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, + BouncyCastleProvider.PROVIDER_NAME); + SecretKeySpec sm4Key = new SecretKeySpec(keyBytes, ALGORITHM_NAME); + cipher.init(Cipher.ENCRYPT_MODE, sm4Key); + byte[] encryptBytes = cipher.doFinal(dataBytes); + String hexSignature = ByteUtils.toHexString(encryptBytes).toUpperCase(); + byte[] signBytes = hexSignature.getBytes(charset); + encrypted = DatatypeConverter.printBase64Binary(signBytes); + return encrypted; + + } catch (Exception e) { + e.printStackTrace(); + } + return encrypted; + } + + /** + * 报文体解密 + * + * @param key + * @param signature + * @return + */ + public static String decrypt(String key, String encrypted) { + + String decrypted = ""; + + try { + + String sha256Key = sha256(key); + String sm3Key = sm3(sha256Key); + String md5Key = md5(sm3Key); + byte[] keyBytes = ByteUtils.fromHexString(md5Key); + + byte[] encryptBytes = DatatypeConverter.parseBase64Binary(encrypted); + String hexSignature = new String(encryptBytes).toLowerCase(); + byte[] cipherBytes = ByteUtils.fromHexString(hexSignature); + + Cipher cipher = Cipher.getInstance(ALGORITHM_NAME_ECB_PADDING, + BouncyCastleProvider.PROVIDER_NAME); + SecretKeySpec sm4Key = new SecretKeySpec(keyBytes, ALGORITHM_NAME); + cipher.init(Cipher.DECRYPT_MODE, sm4Key); + byte[] doFinal = cipher.doFinal(cipherBytes); + decrypted = new String(doFinal); + return decrypted; + + } catch (Exception e) { + e.printStackTrace(); + } + return decrypted; + } + +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2EnDecryptor.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2EnDecryptor.java new file mode 100644 index 00000000..b81ccd98 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2EnDecryptor.java @@ -0,0 +1,79 @@ +package com.hzya.frame.finance.utils.pufabank; + +import java.io.IOException; +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * SM2加解密工具类 + * + */ +public class SM2EnDecryptor { + + /** + * SM2加密 + * + * @param hexStrPub 公钥信息 + * @param data 加密数据信息 + * @return + */ + public static String encrypt(byte[] hexStrPub, byte[] data) { + byte[] source = new byte[data.length]; + byte[] formatedPubKey; + System.arraycopy(data, 0, source, 0, data.length); + SM2Cipher cipher2sm2 = new SM2Cipher(); + SM2Factory sm2Factory = SM2Factory.getInstance(); + if (hexStrPub.length == 64) { + formatedPubKey = new byte[65]; + formatedPubKey[0] = 0x04; + System.arraycopy(hexStrPub, 0, formatedPubKey, 1, hexStrPub.length); + } else { + formatedPubKey = hexStrPub; + } + ECPoint ecPoint = sm2Factory.ecc_curve.decodePoint(formatedPubKey); + ECPoint c1 = cipher2sm2.Init_enc(sm2Factory, ecPoint); + cipher2sm2.Encrypt(source); + byte[] c3 = new byte[32]; + cipher2sm2.Dofinal(c3); + return SM2Util.byteToHex(c1.getEncoded(false)) + SM2Util.byteToHex(source) + SM2Util.byteToHex(c3); + } + + /** + * SM2解密 + * + * @param privateKey 私钥信息 + * @param encryptedData 加密后的数据信息 + * @return + * @throws IOException + */ + public static byte[] decrypt(byte[] privateKey, byte[] encryptedData) throws IOException { + if (privateKey == null || privateKey.length == 0) { + return null; + } + if (encryptedData == null || encryptedData.length == 0) { + return null; + } + // 加密字节数组转换为十六进制的字符串 长度变为encryptedData.length * 2 + String data = SM2Util.byteToHex(encryptedData); + /*** + * 分解加密字串 (C1 = C1标志位2位 + C1实体部分128位 = 130) (C3 = C3实体部分64位 = 64) (C2 = encryptedData.length + * * 2 - C1长度 - C2长度) + */ + byte[] c1Bytes = SM2Util.hexToByte(data.substring(0, 130)); + int c2Len = encryptedData.length - 97; + byte[] c2 = SM2Util.hexToByte(data.substring(130, 130 + 2 * c2Len)); + byte[] c3 = SM2Util.hexToByte(data.substring(130 + 2 * c2Len, 194 + 2 * c2Len)); + + SM2Factory sm2 = SM2Factory.getInstance(); + BigInteger userD = new BigInteger(1, privateKey); + // 通过C1实体字节来生成ECPoint + ECPoint c1 = sm2.ecc_curve.decodePoint(c1Bytes); + SM2Cipher cipher = new SM2Cipher(); + cipher.Init_dec(userD, c1); + cipher.Decrypt(c2); + cipher.Dofinal(c3); + // 返回解密结果 + return c2; + } +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Factory.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Factory.java new file mode 100644 index 00000000..29b52c30 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Factory.java @@ -0,0 +1,197 @@ +package com.hzya.frame.finance.utils.pufabank; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.generators.ECKeyPairGenerator; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECKeyGenerationParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECFieldElement; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.ECFieldElement.Fp; + +/** + * 国密工厂类 + * + */ +@SuppressWarnings(value = { "deprecation", "static-access" }) +public class SM2Factory { + + /*-----------------------国密算法相关参数begin----------- + * ------------------*/ + // A 第一系数 + private static final BigInteger a = new BigInteger( + "fffffffeffffffffffffffffffffffffffffffff00000000fffffffffffffffc", 16); + + // B 第二系数 + private static final BigInteger b = new BigInteger( + "28e9fa9e9d9f5e344d5a9e4bcf6509a7f39789f515ab8f92ddbcbd414d940e93", 16); + + // 曲线X系数 + private static final BigInteger gx = new BigInteger( + "32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7", 16); + + // 曲线Y系数 + private static final BigInteger gy = new BigInteger( + "bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0", 16); + + // 生产者顺序系数 + private static final BigInteger n = new BigInteger( + "fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123", 16); + + // 素数 + private static final BigInteger p = new BigInteger( + "fffffffeffffffffffffffffffffffffffffffff00000000ffffffffffffffff", 16); + + // 因子系数 1 + // private static final int h = 1; + + /*-----------------------国密算法相关参数end-----------------------------*/ + // 一些必要类 + public final ECFieldElement ecc_gx_fieldelement; + + public final ECFieldElement ecc_gy_fieldelement; + + public final ECCurve ecc_curve; + + public final ECPoint ecc_point_g; + + public final ECDomainParameters ecc_bc_spec; + + public final ECKeyPairGenerator ecc_key_pair_generator; + + /** + * 初始化方法 + * + * @return + */ + public static SM2Factory getInstance() { + return new SM2Factory(); + } + + public SM2Factory() { + + this.ecc_gx_fieldelement = new Fp(this.p, this.gx); + this.ecc_gy_fieldelement = new Fp(this.p, this.gy); + + this.ecc_curve = new ECCurve.Fp(this.p, this.a, this.b); + + this.ecc_point_g = new ECPoint.Fp(this.ecc_curve, this.ecc_gx_fieldelement, this.ecc_gy_fieldelement); + this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_point_g, this.n); + + ECKeyGenerationParameters ecc_ecgenparam; + ecc_ecgenparam = new ECKeyGenerationParameters(this.ecc_bc_spec, new SecureRandom()); + + this.ecc_key_pair_generator = new ECKeyPairGenerator(); + this.ecc_key_pair_generator.init(ecc_ecgenparam); + } + + /** + * 根据私钥、曲线参数计算Z + * + * @param userId + * @param userKey + * @return + */ + public byte[] sm2GetZ(byte[] userId, ECPoint userKey) { + + SM3Digest sm3 = new SM3Digest(); + + int len = userId.length * 8; + sm3.update((byte) (len >> 8 & 0xFF)); + sm3.update((byte) (len & 0xFF)); + sm3.update(userId, 0, userId.length); + + byte[] p = SM2Util.byteConvert32Bytes(this.a); + sm3.update(p, 0, p.length); + + p = SM2Util.byteConvert32Bytes(this.b); + sm3.update(p, 0, p.length); + + p = SM2Util.byteConvert32Bytes(this.gx); + sm3.update(p, 0, p.length); + + p = SM2Util.byteConvert32Bytes(this.gy); + sm3.update(p, 0, p.length); + + p = SM2Util.byteConvert32Bytes(userKey.normalize().getXCoord().toBigInteger()); + sm3.update(p, 0, p.length); + + p = SM2Util.byteConvert32Bytes(userKey.normalize().getYCoord().toBigInteger()); + sm3.update(p, 0, p.length); + + byte[] md = new byte[sm3.getDigestSize()]; + sm3.doFinal(md, 0); + return md; + } + + /** + * 签名相关值计算 + * + * @param md + * @param userD + * @param userKey + * @param sm2Result + */ + public void sm2Sign(byte[] md, BigInteger userD, ECPoint userKey, SM2Result sm2Result) { + + BigInteger e = new BigInteger(1, md); + BigInteger k = null; + ECPoint kp = null; + BigInteger r = null; + BigInteger s = null; + do { + do { + // 正式环境 + AsymmetricCipherKeyPair keypair = ecc_key_pair_generator.generateKeyPair(); + ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) keypair.getPrivate(); + ECPublicKeyParameters ecpub = (ECPublicKeyParameters) keypair.getPublic(); + k = ecpriv.getD(); + kp = ecpub.getQ(); + // r + r = e.add(kp.getXCoord().toBigInteger()); + r = r.mod(this.n); + } while (r.equals(BigInteger.ZERO) || r.add(k).equals(this.n)); + + // (1 + dA)~-1 + BigInteger da_1 = userD.add(BigInteger.ONE); + da_1 = da_1.modInverse(this.n); + // s + s = r.multiply(userD); + s = k.subtract(s).mod(this.n); + s = da_1.multiply(s).mod(this.n); + } while (s.equals(BigInteger.ZERO)); + + sm2Result.r = r; + sm2Result.s = s; + } + + /** + * 验签 + * + * @param md sm3摘要 + * @param userKey 根据公钥decode一个ecpoint对象 + * @param r 没有特殊含义 + * @param s 没有特殊含义 + * @param sm2Result 接收参数的对象 + */ + public void sm2Verify(byte md[], ECPoint userKey, BigInteger r, BigInteger s, SM2Result sm2Result) { + + sm2Result.R = null; + BigInteger e = new BigInteger(1, md); + BigInteger t = r.add(s).mod(this.n); + if (t.equals(BigInteger.ZERO)) { + return; + } else { + ECPoint x1y1 = ecc_point_g.multiply(sm2Result.s); + x1y1 = x1y1.add(userKey.multiply(t)); + sm2Result.R = e.add(x1y1.normalize().getXCoord().toBigInteger()).mod(this.n); + return; + } + } + +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Result.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Result.java new file mode 100644 index 00000000..5030f10a --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Result.java @@ -0,0 +1,33 @@ +package com.hzya.frame.finance.utils.pufabank; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * SM2 + * + */ +public class SM2Result { + + public SM2Result() {} + + public BigInteger r;// 签名r + + public BigInteger s; + + public BigInteger R;// 验签R + + public byte[] sa;// 密钥交换 + + public byte[] sb; + + public byte[] s1; + + public byte[] s2; + + public ECPoint keyra; + + public ECPoint keyrb; + +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Sign.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Sign.java new file mode 100644 index 00000000..0f86b01d --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Sign.java @@ -0,0 +1,151 @@ +package com.hzya.frame.finance.utils.pufabank; + +/** + * SM2签名所计算的值 可以根据实际情况增加删除字段属性 + * + */ +public class SM2Sign { + + // 16进制的私钥 + public String sm2_userd; + + // 椭圆曲线点X + public String x_coord; + + // 椭圆曲线点Y + public String y_coord; + + // SM3摘要Z + public String sm3_z; + + // 明文数据16进制 + public String sign_express; + + // SM3摘要值 + public String sm3_digest; + + // R + public String sign_r; + + // S + public String sign_s; + + // R + public String verify_r; + + // S + public String verify_s; + + // 签名值 + public String sm2_sign; + + // sign 签名 verfiy验签 + public String sm2_type; + + // 是否验签成功 true false + public boolean isVerify; + + public String getX_coord() { + return x_coord; + } + + public void setX_coord(String x_coord) { + this.x_coord = x_coord; + } + + public String getY_coord() { + return y_coord; + } + + public void setY_coord(String y_coord) { + this.y_coord = y_coord; + } + + public String getSm3_z() { + return sm3_z; + } + + public void setSm3_z(String sm3_z) { + this.sm3_z = sm3_z; + } + + public String getSm3_digest() { + return sm3_digest; + } + + public void setSm3_digest(String sm3_digest) { + this.sm3_digest = sm3_digest; + } + + public String getSm2_sign() { + return sm2_sign; + } + + public void setSm2_sign(String sm2_sign) { + this.sm2_sign = sm2_sign; + } + + public String getSign_express() { + return sign_express; + } + + public void setSign_express(String sign_express) { + this.sign_express = sign_express; + } + + public String getSm2_userd() { + return sm2_userd; + } + + public void setSm2_userd(String sm2_userd) { + this.sm2_userd = sm2_userd; + } + + public String getSm2_type() { + return sm2_type; + } + + public void setSm2_type(String sm2_type) { + this.sm2_type = sm2_type; + } + + public boolean isVerify() { + return isVerify; + } + + public void setVerify(boolean isVerify) { + this.isVerify = isVerify; + } + + public String getSign_r() { + return sign_r; + } + + public void setSign_r(String sign_r) { + this.sign_r = sign_r; + } + + public String getSign_s() { + return sign_s; + } + + public void setSign_s(String sign_s) { + this.sign_s = sign_s; + } + + public String getVerify_r() { + return verify_r; + } + + public void setVerify_r(String verify_r) { + this.verify_r = verify_r; + } + + public String getVerify_s() { + return verify_s; + } + + public void setVerify_s(String verify_s) { + this.verify_s = verify_s; + } +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2SignVerify.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2SignVerify.java new file mode 100644 index 00000000..56c5e9f3 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2SignVerify.java @@ -0,0 +1,132 @@ +package com.hzya.frame.finance.utils.pufabank; + +import java.io.ByteArrayInputStream; +import java.math.BigInteger; +import java.util.Enumeration; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; + +/** + * 国密算法的签名、验签 + * + */ +public class SM2SignVerify { + + /** + * 默认USERID + */ + public static String USER_ID = "1234567812345678"; + + /** + * 私钥签名 使用SM3进行对明文数据计算一个摘要值 + * + * @param privatekey 私钥 + * @param sourceData 明文数据 + * @return 签名后的值 + * @throws Exception + */ + public static SM2Sign sign(byte[] privatekey, byte[] sourceData) throws Exception { + SM2Sign sm2SignVO = new SM2Sign(); + sm2SignVO.setSm2_type("sign"); + SM2Factory factory = SM2Factory.getInstance(); + BigInteger userD = new BigInteger(1, privatekey); + sm2SignVO.setSm2_userd(userD.toString(16)); + + ECPoint userKey = factory.ecc_point_g.multiply(userD); + sm2SignVO.setX_coord(userKey.getXCoord().toBigInteger().toString(16)); + sm2SignVO.setY_coord(userKey.getYCoord().toBigInteger().toString(16)); + + SM3Digest sm3Digest = new SM3Digest(); + byte[] z = factory.sm2GetZ(USER_ID.getBytes(), userKey); + sm2SignVO.setSm3_z(SM2Util.getHexString(z)); + sm2SignVO.setSign_express(SM2Util.getHexString(sourceData)); + sm3Digest.update(z, 0, z.length); + sm3Digest.update(sourceData, 0, sourceData.length); + byte[] md = new byte[32]; + sm3Digest.doFinal(md, 0); + sm2SignVO.setSm3_digest(SM2Util.getHexString(md)); + + SM2Result sm2Result = new SM2Result(); + factory.sm2Sign(md, userD, userKey, sm2Result); + sm2SignVO.setSign_r(sm2Result.r.toString(16)); + sm2SignVO.setSign_s(sm2Result.s.toString(16)); + + ASN1Integer d_r = new ASN1Integer(sm2Result.r); + ASN1Integer d_s = new ASN1Integer(sm2Result.s); + ASN1EncodableVector v2 = new ASN1EncodableVector(); + v2.add(d_r); + v2.add(d_s); + DERSequence sign = new DERSequence(v2); + String result = ByteUtils.toHexString(sign.getEncoded()); + sm2SignVO.setSm2_sign(result); + return sm2SignVO; + } + + /** + * 验证签名(验签) + * + * @param publicKey 公钥信息 + * @param sourceData 密文信息 + * @param signData 签名信息 + * @return 验签的对象 包含了相关参数和验签结果 + */ + @SuppressWarnings("unchecked") + public static SM2Sign validateSign(byte[] publicKey, byte[] sourceData, byte[] signData) { + try { + byte[] formatedPubKey; + SM2Sign verifyVo = new SM2Sign(); + verifyVo.setSm2_type("verify"); + if (publicKey.length == 64) { + // 添加一字节标识,用于ECPoint解析 + formatedPubKey = new byte[65]; + formatedPubKey[0] = 0x04; + System.arraycopy(publicKey, 0, formatedPubKey, 1, publicKey.length); + } else { + formatedPubKey = publicKey; + } + SM2Factory factory = SM2Factory.getInstance(); + ECPoint userKey = factory.ecc_curve.decodePoint(formatedPubKey); + + SM3Digest sm3Digest = new SM3Digest(); + byte[] z = factory.sm2GetZ(USER_ID.getBytes(), userKey); + verifyVo.setSm3_z(SM2Util.getHexString(z)); + sm3Digest.update(z, 0, z.length); + sm3Digest.update(sourceData, 0, sourceData.length); + byte[] md = new byte[32]; + sm3Digest.doFinal(md, 0); + verifyVo.setSm3_digest(SM2Util.getHexString(md)); + + ByteArrayInputStream bis = new ByteArrayInputStream(signData); + ASN1InputStream dis = new ASN1InputStream(bis); + SM2Result sm2Result = null; + ASN1Primitive derObj = dis.readObject(); + dis.close(); + bis.close(); + Enumeration e = ((ASN1Sequence) derObj).getObjects(); + BigInteger r = ((ASN1Integer) e.nextElement()).getValue(); + BigInteger s = ((ASN1Integer) e.nextElement()).getValue(); + sm2Result = new SM2Result(); + sm2Result.r = r; + sm2Result.s = s; + verifyVo.setVerify_r(sm2Result.r.toString(16)); + verifyVo.setVerify_s(sm2Result.s.toString(16)); + factory.sm2Verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result); + boolean verifyFlag = sm2Result.r.equals(sm2Result.R); + verifyVo.setVerify(verifyFlag); + return verifyVo; + } catch (IllegalArgumentException e) { + System.out.println(e); + return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Util.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Util.java new file mode 100644 index 00000000..98985d83 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM2Util.java @@ -0,0 +1,609 @@ +package com.hzya.frame.finance.utils.pufabank; + +import java.math.BigInteger; + +public class SM2Util { + /** + * 整形转换成网络传输的字节流(字节数组)型数据 + * + * @param num 一个整型数据 + * @return 4个字节的自己数组 + */ + public static byte[] intToBytes(int num) { + byte[] bytes = new byte[4]; + bytes[0] = (byte) (0xff & (num >> 0)); + bytes[1] = (byte) (0xff & (num >> 8)); + bytes[2] = (byte) (0xff & (num >> 16)); + bytes[3] = (byte) (0xff & (num >> 24)); + return bytes; + } + + /** + * 四个字节的字节数据转换成一个整形数据 + * + * @param bytes 4个字节的字节数组 + * @return 一个整型数据 + */ + public static int byteToInt(byte[] bytes) { + int num = 0; + int temp; + temp = (0x000000ff & (bytes[0])) << 0; + num = num | temp; + temp = (0x000000ff & (bytes[1])) << 8; + num = num | temp; + temp = (0x000000ff & (bytes[2])) << 16; + num = num | temp; + temp = (0x000000ff & (bytes[3])) << 24; + num = num | temp; + return num; + } + + /** + * 长整形转换成网络传输的字节流(字节数组)型数据 + * + * @param num 一个长整型数据 + * @return 4个字节的自己数组 + */ + public static byte[] longToBytes(long num) { + byte[] bytes = new byte[8]; + for (int i = 0; i < 8; i++) { + bytes[i] = (byte) (0xff & (num >> (i * 8))); + } + + return bytes; + } + + /** + * 大数字转换字节流(字节数组)型数据 + * + * @param n + * @return + */ + public static byte[] byteConvert32Bytes(BigInteger n) { + byte tmpd[] = (byte[]) null; + if (n == null) { + return null; + } + + if (n.toByteArray().length == 33) { + tmpd = new byte[32]; + System.arraycopy(n.toByteArray(), 1, tmpd, 0, 32); + } else if (n.toByteArray().length == 32) { + tmpd = n.toByteArray(); + } else { + tmpd = new byte[32]; + for (int i = 0; i < 32 - n.toByteArray().length; i++) { + tmpd[i] = 0; + } + System.arraycopy(n.toByteArray(), 0, tmpd, 32 - n.toByteArray().length, n.toByteArray().length); + } + return tmpd; + } + + /** + * 换字节流(字节数组)型数据转大数字 + * + * @param b + * @return + */ + public static BigInteger byteConvertInteger(byte[] b) { + if (b[0] < 0) { + byte[] temp = new byte[b.length + 1]; + temp[0] = 0; + System.arraycopy(b, 0, temp, 1, b.length); + return new BigInteger(temp); + } + return new BigInteger(b); + } + + /** + * 根据字节数组获得值(十六进制数字) + * + * @param bytes + * @return + */ + public static String getHexString(byte[] bytes) { + return getHexString(bytes, true); + } + + /** + * 根据字节数组获得值(十六进制数字) + * + * @param bytes + * @param upperCase + * @return + */ + public static String getHexString(byte[] bytes, boolean upperCase) { + String ret = ""; + for (int i = 0; i < bytes.length; i++) { + ret += Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1); + } + return upperCase ? ret.toUpperCase() : ret; + } + + /** + * 打印十六进制字符串 + * + * @param bytes + */ + public static void printHexString(byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + String hex = Integer.toHexString(bytes[i] & 0xFF); + if (hex.length() == 1) { + hex = '0' + hex; + } + } + } + + /** + * Convert hex string to byte[] + * + * @param hexString the hex string + * @return byte[] + */ + public static byte[] hexStringToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); + } + return d; + } + + /** + * Convert char to byte + * + * @param c char + * @return byte + */ + public static byte charToByte(char c) { + return (byte) "0123456789ABCDEF".indexOf(c); + } + + /** + * 用于建立十六进制字符的输出的小写字符数组 + */ + private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f' }; + + /** + * 用于建立十六进制字符的输出的大写字符数组 + */ + private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F' }; + + /** + * 将字节数组转换为十六进制字符数组 + * + * @param data byte[] + * @return 十六进制char[] + */ + public static char[] encodeHex(byte[] data) { + return encodeHex(data, true); + } + + /** + * 将字节数组转换为十六进制字符数组 + * + * @param data byte[] + * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 + * @return 十六进制char[] + */ + public static char[] encodeHex(byte[] data, boolean toLowerCase) { + return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + /** + * 将字节数组转换为十六进制字符数组 + * + * @param data byte[] + * @param toDigits 用于控制输出的char[] + * @return 十六进制char[] + */ + protected static char[] encodeHex(byte[] data, char[] toDigits) { + int l = data.length; + char[] out = new char[l << 1]; + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } + + /** + * 将字节数组转换为十六进制字符串 + * + * @param data byte[] + * @return 十六进制String + */ + public static String encodeHexString(byte[] data) { + return encodeHexString(data, true); + } + + /** + * 将字节数组转换为十六进制字符串 + * + * @param data byte[] + * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 + * @return 十六进制String + */ + public static String encodeHexString(byte[] data, boolean toLowerCase) { + return encodeHexString(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + /** + * 将字节数组转换为十六进制字符串 + * + * @param data byte[] + * @param toDigits 用于控制输出的char[] + * @return 十六进制String + */ + protected static String encodeHexString(byte[] data, char[] toDigits) { + return new String(encodeHex(data, toDigits)); + } + + /** + * 将十六进制字符数组转换为字节数组 + * + * @param data 十六进制char[] + * @return byte[] + * @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度,将抛出运行时异常 + */ + public static byte[] decodeHex(char[] data) { + int len = data.length; + + if ((len & 0x01) != 0) { + throw new RuntimeException("Odd number of characters."); + } + + byte[] out = new byte[len >> 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; j < len; i++) { + int f = toDigit(data[j], j) << 4; + j++; + f = f | toDigit(data[j], j); + j++; + out[i] = (byte) (f & 0xFF); + } + + return out; + } + + /** + * 将十六进制字符转换成一个整数 + * + * @param ch 十六进制char + * @param index 十六进制字符在字符数组中的位置 + * @return 一个整数 + * @throws RuntimeException 当ch不是一个合法的十六进制字符时,抛出运行时异常 + */ + protected static int toDigit(char ch, int index) { + int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index); + } + return digit; + } + + /** + * 数字字符串转ASCII码字符串 + * + * @param String 字符串 + * @return ASCII字符串 + */ + public static String StringToAsciiString(String content) { + String result = ""; + int max = content.length(); + for (int i = 0; i < max; i++) { + char c = content.charAt(i); + String b = Integer.toHexString(c); + result = result + b; + } + return result; + } + + /** + * 十六进制转字符串 + * + * @param hexString 十六进制字符串 + * @param encodeType 编码类型4:Unicode,2:普通编码 + * @return 字符串 + */ + public static String hexStringToString(String hexString, int encodeType) { + String result = ""; + int max = hexString.length() / encodeType; + for (int i = 0; i < max; i++) { + char c = (char) hexStringToAlgorism(hexString.substring(i * encodeType, (i + 1) * encodeType)); + result += c; + } + return result; + } + + /** + * 十六进制字符串装十进制 + * + * @param hex 十六进制字符串 + * @return 十进制数值 + */ + public static int hexStringToAlgorism(String hex) { + hex = hex.toUpperCase(); + int max = hex.length(); + int result = 0; + for (int i = max; i > 0; i--) { + char c = hex.charAt(i - 1); + int algorism = 0; + if (c >= '0' && c <= '9') { + algorism = c - '0'; + } else { + algorism = c - 55; + } + result += Math.pow(16, max - i) * algorism; + } + return result; + } + + /** + * 十六转二进制 + * + * @param hex 十六进制字符串 + * @return 二进制字符串 + */ + public static String hexStringToBinary(String hex) { + hex = hex.toUpperCase(); + String result = ""; + int max = hex.length(); + for (int i = 0; i < max; i++) { + char c = hex.charAt(i); + switch (c) { + case '0': + result += "0000"; + break; + case '1': + result += "0001"; + break; + case '2': + result += "0010"; + break; + case '3': + result += "0011"; + break; + case '4': + result += "0100"; + break; + case '5': + result += "0101"; + break; + case '6': + result += "0110"; + break; + case '7': + result += "0111"; + break; + case '8': + result += "1000"; + break; + case '9': + result += "1001"; + break; + case 'A': + result += "1010"; + break; + case 'B': + result += "1011"; + break; + case 'C': + result += "1100"; + break; + case 'D': + result += "1101"; + break; + case 'E': + result += "1110"; + break; + case 'F': + result += "1111"; + break; + } + } + return result; + } + + /** + * ASCII码字符串转数字字符串 + * + * @param String ASCII字符串 + * @return 字符串 + */ + public static String AsciiStringToString(String content) { + String result = ""; + int length = content.length() / 2; + for (int i = 0; i < length; i++) { + String c = content.substring(i * 2, i * 2 + 2); + int a = hexStringToAlgorism(c); + char b = (char) a; + String d = String.valueOf(b); + result += d; + } + return result; + } + + /** + * 将十进制转换为指定长度的十六进制字符串 + * + * @param algorism int 十进制数字 + * @param maxLength int 转换后的十六进制字符串长度 + * @return String 转换后的十六进制字符串 + */ + public static String algorismToHexString(int algorism, int maxLength) { + String result = ""; + result = Integer.toHexString(algorism); + + if (result.length() % 2 == 1) { + result = "0" + result; + } + return patchHexString(result.toUpperCase(), maxLength); + } + + /** + * 字节数组转为普通字符串(ASCII对应的字符) + * + * @param bytearray byte[] + * @return String + */ + public static String byteToString(byte[] bytearray) { + String result = ""; + char temp; + + int length = bytearray.length; + for (int i = 0; i < length; i++) { + temp = (char) bytearray[i]; + result += temp; + } + return result; + } + + /** + * 二进制字符串转十进制 + * + * @param binary 二进制字符串 + * @return 十进制数值 + */ + public static int binaryToAlgorism(String binary) { + int max = binary.length(); + int result = 0; + for (int i = max; i > 0; i--) { + char c = binary.charAt(i - 1); + int algorism = c - '0'; + result += Math.pow(2, max - i) * algorism; + } + return result; + } + + /** + * 十进制转换为十六进制字符串 + * + * @param algorism int 十进制的数字 + * @return String 对应的十六进制字符串 + */ + public static String algorismToHEXString(int algorism) { + String result = ""; + result = Integer.toHexString(algorism); + + if (result.length() % 2 == 1) { + result = "0" + result; + + } + result = result.toUpperCase(); + + return result; + } + + /** + * HEX字符串前补0,主要用于长度位数不足。 + * + * @param str String 需要补充长度的十六进制字符串 + * @param maxLength int 补充后十六进制字符串的长度 + * @return 补充结果 + */ + static public String patchHexString(String str, int maxLength) { + String temp = ""; + for (int i = 0; i < maxLength - str.length(); i++) { + temp = "0" + temp; + } + str = (temp + str).substring(0, maxLength); + return str; + } + + /** + * 将一个字符串转换为int + * + * @param s String 要转换的字符串 + * @param defaultInt int 如果出现异常,默认返回的数字 + * @param radix int 要转换的字符串是什么进制的,如16 8 10. + * @return int 转换后的数字 + */ + public static int parseToInt(String s, int defaultInt, int radix) { + int i = 0; + try { + i = Integer.parseInt(s, radix); + } catch (NumberFormatException ex) { + i = defaultInt; + } + return i; + } + + /** + * 将一个十进制形式的数字字符串转换为int + * + * @param s String 要转换的字符串 + * @param defaultInt int 如果出现异常,默认返回的数字 + * @return int 转换后的数字 + */ + public static int parseToInt(String s, int defaultInt) { + int i = 0; + try { + i = Integer.parseInt(s); + } catch (NumberFormatException ex) { + i = defaultInt; + } + return i; + } + + /** + * 十六进制串转化为byte数组 + * + * @return the array of byte + */ + public static byte[] hexToByte(String hex) throws IllegalArgumentException { + if (hex.length() % 2 != 0) { + throw new IllegalArgumentException(); + } + char[] arr = hex.toCharArray(); + byte[] b = new byte[hex.length() / 2]; + for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) { + String swap = "" + arr[i++] + arr[i]; + int byteint = Integer.parseInt(swap, 16) & 0xFF; + b[j] = new Integer(byteint).byteValue(); + } + return b; + } + + /** + * 字节数组转换为十六进制字符串 + * + * @param b byte[] 需要转换的字节数组 + * @return String 十六进制字符串 + */ + public static String byteToHex(byte b[]) { + if (b == null) { + throw new IllegalArgumentException("Argument b ( byte array ) is null! "); + } + String hs = ""; + String stmp = ""; + for (int n = 0; n < b.length; n++) { + stmp = Integer.toHexString(b[n] & 0xff); + if (stmp.length() == 1) { + hs = hs + "0" + stmp; + } else { + hs = hs + stmp; + } + } + return hs.toUpperCase(); + } + + public static byte[] subByte(byte[] input, int startIndex, int length) { + byte[] bt = new byte[length]; + for (int i = 0; i < length; i++) { + bt[i] = input[i + startIndex]; + } + return bt; + } +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM3.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM3.java new file mode 100644 index 00000000..725ab141 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM3.java @@ -0,0 +1,258 @@ +package com.hzya.frame.finance.utils.pufabank; + +/** + * SM3 + * + */ +public class SM3 { + + public static final byte[] iv = { 0x73, (byte) 0x80, 0x16, 0x6f, 0x49, 0x14, (byte) 0xb2, (byte) 0xb9, + 0x17, 0x24, 0x42, (byte) 0xd7, (byte) 0xda, (byte) 0x8a, 0x06, 0x00, (byte) 0xa9, 0x6f, 0x30, + (byte) 0xbc, (byte) 0x16, 0x31, 0x38, (byte) 0xaa, (byte) 0xe3, (byte) 0x8d, (byte) 0xee, 0x4d, + (byte) 0xb0, (byte) 0xfb, 0x0e, 0x4e }; + + public static int[] Tj = new int[64]; + + static { + for (int i = 0; i < 16; i++) { + Tj[i] = 0x79cc4519; + } + + for (int i = 16; i < 64; i++) { + Tj[i] = 0x7a879d8a; + } + } + + public static byte[] CF(byte[] V, byte[] B) { + int[] v, b; + v = convert(V); + b = convert(B); + return convert(CF(v, b)); + } + + private static int[] convert(byte[] arr) { + int[] out = new int[arr.length / 4]; + byte[] tmp = new byte[4]; + for (int i = 0; i < arr.length; i += 4) { + System.arraycopy(arr, i, tmp, 0, 4); + out[i / 4] = bigEndianByteToInt(tmp); + } + return out; + } + + private static byte[] convert(int[] arr) { + byte[] out = new byte[arr.length * 4]; + byte[] tmp = null; + for (int i = 0; i < arr.length; i++) { + tmp = bigEndianIntToByte(arr[i]); + System.arraycopy(tmp, 0, out, i * 4, 4); + } + return out; + } + + public static int[] CF(int[] V, int[] B) { + int a, b, c, d, e, f, g, h; + int ss1, ss2, tt1, tt2; + a = V[0]; + b = V[1]; + c = V[2]; + d = V[3]; + e = V[4]; + f = V[5]; + g = V[6]; + h = V[7]; + + int[][] arr = expand(B); + int[] w = arr[0]; + int[] w1 = arr[1]; + + for (int j = 0; j < 64; j++) { + ss1 = (bitCycleLeft(a, 12) + e + bitCycleLeft(Tj[j], j)); + ss1 = bitCycleLeft(ss1, 7); + ss2 = ss1 ^ bitCycleLeft(a, 12); + tt1 = FFj(a, b, c, j) + d + ss2 + w1[j]; + tt2 = GGj(e, f, g, j) + h + ss1 + w[j]; + d = c; + c = bitCycleLeft(b, 9); + b = a; + a = tt1; + h = g; + g = bitCycleLeft(f, 19); + f = e; + e = P0(tt2); + } + + int[] out = new int[8]; + out[0] = a ^ V[0]; + out[1] = b ^ V[1]; + out[2] = c ^ V[2]; + out[3] = d ^ V[3]; + out[4] = e ^ V[4]; + out[5] = f ^ V[5]; + out[6] = g ^ V[6]; + out[7] = h ^ V[7]; + + return out; + } + + private static int[][] expand(int[] B) { + int W[] = new int[68]; + int W1[] = new int[64]; + for (int i = 0; i < B.length; i++) { + W[i] = B[i]; + } + + for (int i = 16; i < 68; i++) { + W[i] = P1(W[i - 16] ^ W[i - 9] ^ bitCycleLeft(W[i - 3], 15)) ^ bitCycleLeft(W[i - 13], 7) + ^ W[i - 6]; + } + + for (int i = 0; i < 64; i++) { + W1[i] = W[i] ^ W[i + 4]; + } + + int arr[][] = new int[][] { W, W1 }; + return arr; + } + + private static byte[] bigEndianIntToByte(int num) { + return back(SM2Util.intToBytes(num)); + } + + private static int bigEndianByteToInt(byte[] bytes) { + return SM2Util.byteToInt(back(bytes)); + } + + private static int FFj(int X, int Y, int Z, int j) { + if (j >= 0 && j <= 15) { + return FF1j(X, Y, Z); + } else { + return FF2j(X, Y, Z); + } + } + + private static int GGj(int X, int Y, int Z, int j) { + if (j >= 0 && j <= 15) { + return GG1j(X, Y, Z); + } else { + return GG2j(X, Y, Z); + } + } + + // 逻辑位运算函数 + private static int FF1j(int X, int Y, int Z) { + int tmp = X ^ Y ^ Z; + return tmp; + } + + private static int FF2j(int X, int Y, int Z) { + int tmp = ((X & Y) | (X & Z) | (Y & Z)); + return tmp; + } + + private static int GG1j(int X, int Y, int Z) { + int tmp = X ^ Y ^ Z; + return tmp; + } + + private static int GG2j(int X, int Y, int Z) { + int tmp = (X & Y) | (~X & Z); + return tmp; + } + + private static int P0(int X) { + int y = rotateLeft(X, 9); + y = bitCycleLeft(X, 9); + int z = rotateLeft(X, 17); + z = bitCycleLeft(X, 17); + int t = X ^ y ^ z; + return t; + } + + private static int P1(int X) { + int t = X ^ bitCycleLeft(X, 15) ^ bitCycleLeft(X, 23); + return t; + } + + /** + * 对最后一个分组字节数据padding + * + * @param in + * @param bLen 分组个数 + * @return + */ + public static byte[] padding(byte[] in, int bLen) { + int k = 448 - (8 * in.length + 1) % 512; + if (k < 0) { + k = 960 - (8 * in.length + 1) % 512; + } + k += 1; + byte[] padd = new byte[k / 8]; + padd[0] = (byte) 0x80; + long n = in.length * 8 + bLen * 512; + byte[] out = new byte[in.length + k / 8 + 64 / 8]; + int pos = 0; + System.arraycopy(in, 0, out, 0, in.length); + pos += in.length; + System.arraycopy(padd, 0, out, pos, padd.length); + pos += padd.length; + byte[] tmp = back(SM2Util.longToBytes(n)); + System.arraycopy(tmp, 0, out, pos, tmp.length); + return out; + } + + /** + * 字节数组逆序 + * + * @param in + * @return + */ + private static byte[] back(byte[] in) { + byte[] out = new byte[in.length]; + for (int i = 0; i < out.length; i++) { + out[i] = in[out.length - i - 1]; + } + + return out; + } + + public static int rotateLeft(int x, int n) { + return (x << n) | (x >> (32 - n)); + } + + private static int bitCycleLeft(int n, int bitLen) { + bitLen %= 32; + byte[] tmp = bigEndianIntToByte(n); + int byteLen = bitLen / 8; + int len = bitLen % 8; + if (byteLen > 0) { + tmp = byteCycleLeft(tmp, byteLen); + } + + if (len > 0) { + tmp = bitSmall8CycleLeft(tmp, len); + } + + return bigEndianByteToInt(tmp); + } + + private static byte[] bitSmall8CycleLeft(byte[] in, int len) { + byte[] tmp = new byte[in.length]; + int t1, t2, t3; + for (int i = 0; i < tmp.length; i++) { + t1 = (byte) ((in[i] & 0x000000ff) << len); + t2 = (byte) ((in[(i + 1) % tmp.length] & 0x000000ff) >> (8 - len)); + t3 = (byte) (t1 | t2); + tmp[i] = (byte) t3; + } + + return tmp; + } + + private static byte[] byteCycleLeft(byte[] in, int byteLen) { + byte[] tmp = new byte[in.length]; + System.arraycopy(in, byteLen, tmp, 0, in.length - byteLen); + System.arraycopy(in, 0, tmp, in.length - byteLen, byteLen); + return tmp; + } +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM3Digest.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM3Digest.java new file mode 100644 index 00000000..9fdc3f23 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SM3Digest.java @@ -0,0 +1,128 @@ +package com.hzya.frame.finance.utils.pufabank; + +import org.bouncycastle.util.encoders.Hex; + +public class SM3Digest { + + /** SM3值的长度 */ + private static final int BYTE_LENGTH = 32; + + /** SM3分组长度 */ + private static final int BLOCK_LENGTH = 64; + + /** 缓冲区长度 */ + private static final int BUFFER_LENGTH = BLOCK_LENGTH * 1; + + /** 缓冲区 */ + private byte[] xBuf = new byte[BUFFER_LENGTH]; + + /** 缓冲区偏移量 */ + private int xBufOff; + + /** 初始向量 */ + private byte[] V = SM3.iv.clone(); + + private int cntBlock = 0; + + public SM3Digest() { + } + + public SM3Digest(SM3Digest t) { + System.arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length); + this.xBufOff = t.xBufOff; + System.arraycopy(t.V, 0, this.V, 0, t.V.length); + } + + /** + * SM3结果输出 + * + * @param out 保存SM3结构的缓冲区 + * @param outOff 缓冲区偏移量 + * @return + */ + public int doFinal(byte[] out, int outOff) { + byte[] tmp = doFinal(); + System.arraycopy(tmp, 0, out, 0, tmp.length); + return BYTE_LENGTH; + } + + public void reset() { + xBufOff = 0; + cntBlock = 0; + V = SM3.iv.clone(); + } + + /** + * 明文输入 + * + * @param in 明文输入缓冲区 + * @param inOff 缓冲区偏移量 + * @param len 明文长度 + */ + public void update(byte[] in, int inOff, int len) { + int partLen = BUFFER_LENGTH - xBufOff; + int inputLen = len; + int dPos = inOff; + if (partLen < inputLen) { + System.arraycopy(in, dPos, xBuf, xBufOff, partLen); + inputLen -= partLen; + dPos += partLen; + doUpdate(); + while (inputLen > BUFFER_LENGTH) { + System.arraycopy(in, dPos, xBuf, 0, BUFFER_LENGTH); + inputLen -= BUFFER_LENGTH; + dPos += BUFFER_LENGTH; + doUpdate(); + } + } + + System.arraycopy(in, dPos, xBuf, xBufOff, inputLen); + xBufOff += inputLen; + } + + private void doUpdate() { + byte[] B = new byte[BLOCK_LENGTH]; + for (int i = 0; i < BUFFER_LENGTH; i += BLOCK_LENGTH) { + System.arraycopy(xBuf, i, B, 0, B.length); + doHash(B); + } + xBufOff = 0; + } + + private void doHash(byte[] B) { + byte[] tmp = SM3.CF(V, B); + System.arraycopy(tmp, 0, V, 0, V.length); + cntBlock++; + } + + private byte[] doFinal() { + byte[] B = new byte[BLOCK_LENGTH]; + byte[] buffer = new byte[xBufOff]; + System.arraycopy(xBuf, 0, buffer, 0, buffer.length); + byte[] tmp = SM3.padding(buffer, cntBlock); + for (int i = 0; i < tmp.length; i += BLOCK_LENGTH) { + System.arraycopy(tmp, i, B, 0, B.length); + doHash(B); + } + return V; + } + + public void update(byte in) { + byte[] buffer = new byte[] { in }; + update(buffer, 0, 1); + } + + public int getDigestSize() { + return BYTE_LENGTH; + } + + public static void main(String[] args) { + byte[] md = new byte[32]; + byte[] msg1 = "ererfeiisgod".getBytes(); + SM3Digest sm3 = new SM3Digest(); + sm3.update(msg1, 0, msg1.length); + sm3.doFinal(md, 0); + String s = new String(Hex.encode(md)); + System.out.println(s); + } +} diff --git a/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SPDBSMEncryptor.java b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SPDBSMEncryptor.java new file mode 100644 index 00000000..17a036c1 --- /dev/null +++ b/base-buildpackage/src/main/java/com/hzya/frame/finance/utils/pufabank/SPDBSMEncryptor.java @@ -0,0 +1,171 @@ +package com.hzya.frame.finance.utils.pufabank; + +import okhttp3.*; +import org.bouncycastle.pqc.math.linearalgebra.ByteUtils; + +import javax.xml.bind.DatatypeConverter; +import java.io.UnsupportedEncodingException; +import java.util.concurrent.TimeUnit; + +public class SPDBSMEncryptor { + + /*ClientID和密钥:X-SPDB-ClientID、X-SPDB-ClientID-Secret + * 浦发API开发平台上创建APP给您邮件返回的,或我们提供给您测试使用的 + * 生产请使用API开发平台上创建APP给您邮件返回的生产环境密钥 + */ + private static final String clientId = "f42d86e9-debd-4423-84af-74549a51c7c2"; + //加解密样例仅供参考,秘钥有过期时间,建议在生产环境不要将秘钥硬编码在代码中 + private static final String secret = "NmZkMC00ZDMpLWFyYzgtN2JwN2ZmY2J1NmF4MC40NzQ0ODA5MzA2MzE4OTg2NzAu"; + + /* + *请求头ContentType,一般为application/json,API对外接口文档中有报文体格式 + *图文上传接口为form-data,如果是图文信息上传,请参考国密普通验签图片上传接口测试工程.zip + */ + private static final String contentType = "application/json;charset=utf-8"; + + /*您的私钥,测试环境中请将您对应的公钥请提供给我们,我们需要在门户配置,否则无法通过验签 + * 公私钥生成可进入本项目下nodejs文件夹下运行 node getKeyPaireHex.js 即可生成公私密钥 + * 加解密样例仅供参考,秘钥有过期时间,建议在生产环境不要将秘钥硬编码在代码中 + */ + private static final String privatekey = "ae14b3717d6b44b55e500a807248aab425e1d1677df1718410c453f3fe257541"; + + /* + *浦发公钥,在用户中心我的APP开发者公钥中查看 + *加解密样例仅供参考,秘钥有过期时间,建议在生产环境不要将秘钥硬编码在代码中 + */ + private static final String spdb_publicKey = "04e63345389de1dbc822881f80c2a82e95f638dd9881ebea8bb63a00ba5580559f77db5cc08adc087428fdbab81fe4dc580d48735cd111bdc9ae898f7b57628f09"; + + /** + * 请求编码格式 + */ + private static final String charset = "UTF-8"; + + + private static final OkHttpClient client = new OkHttpClient.Builder() + .connectionPool(new ConnectionPool(500, 5, TimeUnit.MINUTES)).connectTimeout(10000, TimeUnit.MILLISECONDS) + .readTimeout(60000, TimeUnit.MILLISECONDS).build(); + + + /** + * 获取防重放参数 + * @param forbidden 是否需使用防重放参数 + * @param data 报文体 + * @return + */ + public static String getNonceParam( boolean forbidden ,String data){ + //防重放参数。毫秒级的时间戳,与浦发服务器(北京时间)时间误差不能超过15分钟。样例:1543461367533 + long timestamp = 0L; + //防重放参数。X-SPDB-Nonce为交易序号,需保证唯一。样例:TRANS10145581 + String nonce = ""; + /* + * 防重放参数 + * 如需使用防重放参数,需要对‘时间戳’和‘防重放参数’加签 + */ + String newBodyData = data; + + if (forbidden) {//如需使用防重放参数 + //对应请求报文头参数 X-SPDB-Timestamp + timestamp = System.currentTimeMillis();// X-SPDB-Timestamp + //对应请求报文头参数 X-SPDB-Nonce + nonce = "TRAN10145581";// X-SPDB-Nonce + //注意:签名原文需要拼接防重放参数 例:{"name":"zhangshan"}1591077686410TRAN10145581 + newBodyData = data + timestamp + nonce; + System.out.println("签名原文:" + newBodyData); + } + return newBodyData; + } + + + public static Request createRequest(String requestMethod,String url,String data,String signature){ + Request request =null; + String encrypted = SM2Cryptor.encrypt(secret, data); + if("GET".equals(requestMethod) || "get".equals(requestMethod)){ + url = url + "?encryptBody=" + encrypted; + request = new Request.Builder().url(url).get() + .addHeader("Content-Type", contentType) //Content-Type + .addHeader("X-SPDB-Client-ID", clientId) //Client-ID + .addHeader("X-SPDB-SIGNATURE", signature) //签名结果 + .addHeader("X-SPDB-SM", "true") //使用国密 + .addHeader("X-SPDB-Encryption", "true") //使用全报文加密 + //X-SPDB-Timestamp、X-SPDB-Nonce两个参数为防重放参数,不需要时不传 + //.addHeader("X-SPDB-Timestamp", String.valueOf(timestamp)) //防重放参数-毫秒级时间戳 + //.addHeader("X-SPDB-Nonce", nonce) //防重放参数 + .build(); + + }else if("POST".equals(requestMethod) || "post".equals(requestMethod)){ + //发送HTTP请求 + MediaType mediaType = MediaType.parse(contentType); + // 数据加密-使用国密SM2算法加密 注意加密数据不添加防重放参数 + RequestBody body = RequestBody.create(mediaType, encrypted); + request = new Request.Builder().url(url).post(body) + .addHeader("Content-Type", contentType) //Content-Type + .addHeader("X-SPDB-Client-ID", clientId) //Client-ID + .addHeader("X-SPDB-SIGNATURE", signature) //签名结果 + .addHeader("X-SPDB-SM", "true") //使用国密 + .addHeader("X-SPDB-Encryption", "true") //使用全报文加密 + //X-SPDB-Timestamp、X-SPDB-Nonce两个参数为防重放参数,不需要时不传 + //.addHeader("X-SPDB-Timestamp", String.valueOf(timestamp)) //防重放参数-毫秒级时间戳 + //.addHeader("X-SPDB-Nonce", nonce) //防重放参数 + .build(); + } + return request; + } + + + public static void main(String[] args) throws Exception { + try { + /** + * 请求的URL,请使用对API外接口文档中提供的URL + */ + String url = "https://etest4.spdb.com.cn/spdb/uat/api/corporateAccounts/banks/transDetails"; + /* + * 请求报文体 {"name":"zhangshan"} 注意换行符和空格 + * 如果是图文信息上传,请参考国密普通验签图片上传接口测试工程.zip + */ + String data = "{\"clientNo\": \"2678987519\",\"pyAcctNo\": \"88010078801000025122\",\"startDate\":\"20250801\",\"endDate\": \"20250831\",\"acptNum\": \"1\",\"queryNum\": \"10\"}"; + System.out.println("原报文体:" + data); + /** + * 是否获取防重放参数 + */ + String newBodyData = getNonceParam(false, data); + + // 数据加密-使用国密SM2算法加密 注意加密数据不添加防重放参数 + String encrypted = SM2Cryptor.encrypt(secret, data); + System.out.println("加密报文体:" + encrypted); + // 数据解密 + String decrypted = SM2Cryptor.decrypt(secret, encrypted); + System.out.println("解密报文体:" + decrypted); + + byte[] dataBytes = newBodyData.getBytes(charset); + + SM2Sign sign = SM2SignVerify.sign(ByteUtils.fromHexString(privatekey), dataBytes); + byte[] signBytes = sign.getSm2_sign().getBytes(charset); + String signature = DatatypeConverter.printBase64Binary(signBytes); + System.out.println("signature:" + signature); + + //发送HTTP请求 + Request request = createRequest("post", url, data, signature); + Response response = client.newCall(request).execute(); + + String resBody = new String(response.body().bytes(), "UTF-8"); + System.out.println("加密响应报文:" + resBody); + + String decryptBody = SM2Cryptor.decrypt(secret, resBody); + System.out.println("明文响应报文:" + decryptBody); + + String sg = new String(response.header("X-SPDB-SIGNATURE").getBytes(), "UTF-8"); + + String digest = SHA1.digest(decryptBody); + byte[] bytes = digest.getBytes(charset); + + byte[] signatureBytes = ByteUtils.fromHexString(new String(DatatypeConverter.parseBase64Binary(sg))); + SM2Sign sm2Sign = SM2SignVerify.validateSign(ByteUtils.fromHexString(spdb_publicKey), bytes, signatureBytes); + + boolean validateSign = sm2Sign.isVerify(); + System.out.println("验签结果:" + validateSign); + }catch (Exception e){ + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/base-buildpackage/src/main/resources/lib/bcmail-jdk15on-1.56.jar b/base-buildpackage/src/main/resources/lib/bcmail-jdk15on-1.56.jar new file mode 100644 index 00000000..2d27a4d8 Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/bcmail-jdk15on-1.56.jar differ diff --git a/base-buildpackage/src/main/resources/lib/bcpkix-jdk15on-1.57.jar b/base-buildpackage/src/main/resources/lib/bcpkix-jdk15on-1.57.jar new file mode 100644 index 00000000..5ce7d5c5 Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/bcpkix-jdk15on-1.57.jar differ diff --git a/base-buildpackage/src/main/resources/lib/bcprov-jdk15on-1.57.jar b/base-buildpackage/src/main/resources/lib/bcprov-jdk15on-1.57.jar new file mode 100644 index 00000000..5a10986b Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/bcprov-jdk15on-1.57.jar differ diff --git a/base-buildpackage/src/main/resources/lib/commons-codec-1.9.jar b/base-buildpackage/src/main/resources/lib/commons-codec-1.9.jar new file mode 100644 index 00000000..ef35f1c5 Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/commons-codec-1.9.jar differ diff --git a/base-buildpackage/src/main/resources/lib/commons-lang3-3.9.jar b/base-buildpackage/src/main/resources/lib/commons-lang3-3.9.jar new file mode 100644 index 00000000..0d896939 Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/commons-lang3-3.9.jar differ diff --git a/base-buildpackage/src/main/resources/lib/hutool-all-3.0.9.jar b/base-buildpackage/src/main/resources/lib/hutool-all-3.0.9.jar new file mode 100644 index 00000000..9e9bcce2 Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/hutool-all-3.0.9.jar differ diff --git a/base-buildpackage/src/main/resources/lib/okhttp-3.3.0.jar b/base-buildpackage/src/main/resources/lib/okhttp-3.3.0.jar new file mode 100644 index 00000000..cbcfa64b Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/okhttp-3.3.0.jar differ diff --git a/base-buildpackage/src/main/resources/lib/okio-1.8.0.jar b/base-buildpackage/src/main/resources/lib/okio-1.8.0.jar new file mode 100644 index 00000000..bb70f758 Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/okio-1.8.0.jar differ diff --git a/base-buildpackage/src/main/resources/lib/spdb-api-1.0-SNAPSHOT.jar b/base-buildpackage/src/main/resources/lib/spdb-api-1.0-SNAPSHOT.jar new file mode 100644 index 00000000..75865b95 Binary files /dev/null and b/base-buildpackage/src/main/resources/lib/spdb-api-1.0-SNAPSHOT.jar differ