From 29113c35f16cf39bd67901fdacddb9d7666ddd28 Mon Sep 17 00:00:00 2001 From: hecan <1718492867@qq.com> Date: Wed, 26 Mar 2025 17:04:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AD=E4=BF=A1=E5=85=AC=E7=A7=81=E9=92=A5?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/hzya/frame/seeyon/util/SM2Util.java | 353 ++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 fw-oa/src/main/java/com/hzya/frame/seeyon/util/SM2Util.java diff --git a/fw-oa/src/main/java/com/hzya/frame/seeyon/util/SM2Util.java b/fw-oa/src/main/java/com/hzya/frame/seeyon/util/SM2Util.java new file mode 100644 index 00000000..47ff93c4 --- /dev/null +++ b/fw-oa/src/main/java/com/hzya/frame/seeyon/util/SM2Util.java @@ -0,0 +1,353 @@ +package com.hzya.frame.seeyon.util; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.security.spec.*; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +public class SM2Util { + + private final static SM2Engine.Mode DIGEST = SM2Engine.Mode.C1C3C2; + + private static final String PRIVATE_KEY = "privateKey"; + + private static final String PUBLIC_KEY = "publicKey"; + + private static final String STD_NAME = "sm2p256v1"; + + private static final String ALGORITHM = "EC"; + + public static final String LF = "\n"; + + public static final String CR = "\r"; + + public static final String SPACE = " "; + + public static final String EMPTY = ""; + + + /** + * 生成密钥 + * + * @return + */ + public Map generate() { + Map keyMap = new HashMap<>(); + try { + ECGenParameterSpec sm2Spec = new ECGenParameterSpec(STD_NAME); + KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGORITHM, new BouncyCastleProvider()); + kpg.initialize(sm2Spec); + KeyPair keyPair = kpg.generateKeyPair(); + PublicKey publicKey = keyPair.getPublic(); + PrivateKey privateKey = keyPair.getPrivate(); + keyMap.put(PRIVATE_KEY, new String(Base64.getEncoder().encode(privateKey.getEncoded()), StandardCharsets.UTF_8)); + keyMap.put(PUBLIC_KEY, new String(Base64.getEncoder().encode(publicKey.getEncoded()), StandardCharsets.UTF_8)); + } catch (Exception e) { + /*this.log.info("SM2Util生成密钥出现异常", e);*/ + } + return keyMap; + } + + /** + * 加密 + * + * @param data 数据Str + * @param publicKey 公钥Str + * @return 加密之后的数据 + */ + public static String encrypt(String data, String publicKey) { + try { + return new String(Base64.getEncoder().encode(encrypt(data.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(publicKey.getBytes(StandardCharsets.UTF_8))))); + }catch (Exception e){ + e.printStackTrace(); + } + return null; + } + + /** + * 加密 + * + * @param data 数据 + * @param publicKey 公钥 + * @return 加密之后的数据 + */ + public static byte[] encrypt(byte[] data, byte[] publicKey) throws Exception { + CipherParameters pubKeyParameters = new ParametersWithRandom(publicKeyToParams("SM2", publicKey)); + SM2Engine engine = new SM2Engine(DIGEST); + engine.init(true, pubKeyParameters); + return engine.processBlock(data, 0, data.length); + } + + /** + * 解密 + * + * @param data 数据 + * @param privateKey 私钥 + * @return 解密之后的数据 + */ + public static String decrypt(String data, String privateKey) throws Exception { + return new String(decrypt(Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)), Base64.getDecoder().decode(privateKey.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8); + } + + /** + * 解密 + * + * @param data 数据 + * @param privateKey 私钥 + * @return 解密之后的数据 + */ + public static byte[] decrypt(byte[] data, byte[] privateKey) throws Exception { + CipherParameters privateKeyParameters = privateKeyToParams("SM2", privateKey); + SM2Engine engine = new SM2Engine(DIGEST); + engine.init(false, privateKeyParameters); + return engine.processBlock(data, 0, data.length); + } + + /** + * SM2密文转换空格等特殊字符 + * + * @param sm2Content + * @return + */ + public static String fixSm2Content(String sm2Content) { + if (sm2Content == null) { + return sm2Content; + } + return sm2Content.replace(LF, EMPTY).replace(CR, EMPTY).replace(SPACE, "+"); + } + + /** + * 私钥转换为 {@link ECPrivateKeyParameters} + * + * @param key key + * @return + * @throws InvalidKeyException + */ + public static ECPrivateKeyParameters privateKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException { + if (key == null || key.length == 0) { + throw new NullPointerException("key must be not null !"); + } + PrivateKey privateKey = generatePrivateKey(algorithm, key); + return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey); + } + + /** + * 生成私钥 + * + * @param algorithm 算法 + * @param key key + * @return + */ + public static PrivateKey generatePrivateKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException { + if (isBlank(algorithm)) { + throw new NullPointerException("algorithm must be not null !"); + } + if (key == null || key.length == 0) { + throw new NullPointerException("key must be not null !"); + } + KeySpec keySpec = new PKCS8EncodedKeySpec(key); + algorithm = getAlgorithmAfterWith(algorithm); + return getKeyFactory(algorithm).generatePrivate(keySpec); + } + + /** + * 公钥转换为 {@link ECPublicKeyParameters} + * + * @param key key + * @return + * @throws InvalidKeyException + */ + public static ECPublicKeyParameters publicKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException { + if (isBlank(algorithm)) { + throw new NullPointerException("algorithm must be not null !"); + } + if (key == null || key.length == 0) { + throw new NullPointerException("key must be not null !"); + } + PublicKey publicKey = generatePublicKey(algorithm, key); + return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey); + } + + /** + * 生成公钥 + * + * @param algorithm 算法 + * @param key key + * @return + */ + public static PublicKey generatePublicKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException { + if (isBlank(algorithm)) { + throw new NullPointerException("algorithm must be not null !"); + } + if (key == null || key.length == 0) { + throw new NullPointerException("key must be not null !"); + } + KeySpec keySpec = new X509EncodedKeySpec(key); + algorithm = getAlgorithmAfterWith(algorithm); + return getKeyFactory(algorithm).generatePublic(keySpec); + } + + /** + * 获取用于密钥生成的算法
+ * 获取XXXwithXXX算法的后半部分算法,如果为ECDSA或SM2,返回算法为EC + * + * @param algorithm XXXwithXXX算法 + * @return 算法 + */ + private static String getAlgorithmAfterWith(String algorithm) { + if (isBlank(algorithm)) { + throw new NullPointerException("algorithm must be not null !"); + } + int indexOfWith = lastIndexOfIgnoreCase(algorithm, "with"); + if (indexOfWith > 0) { + algorithm = substring(algorithm, indexOfWith + "with".length()); + } + if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm)) { + algorithm = ALGORITHM; + } + return algorithm; + } + + /** + * 获取{@link KeyFactory} + * + * @param algorithm 非对称加密算法 + * @return {@link KeyFactory} + */ + private static KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException { + final Provider provider = new BouncyCastleProvider(); + return KeyFactory.getInstance(algorithm, provider); + } + + public static void main(String[] args) throws Exception { + SM2Util sm2Utils = new SM2Util(); + sm2Utils.testSignByQuickpass(); + } + + private void testSignByQuickpass() throws Exception { + //密钥生成 + String privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgtzMo2o6THK3yLIm+83Ch/560+02l2hjjBSFGieWY/Z6gCgYIKoEcz1UBgi2hRANCAATKhwZX4P3XI8vYTKeCOLMVbanUNbaXjrIEZynshwdOzRVgzRQSiPNWo6OBBkAPvqE+2RS+5ABpS82DSlKl81z0"; + String publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEyocGV+D91yPL2EyngjizFW2p1DW2l46yBGcp7IcHTs0VYM0UEojzVqOjgQZAD76hPtkUvuQAaUvNg0pSpfNc9A=="; + // 加验签 + //String value = "{\"partner\": \"test\",\"tranTime\": \"20240128003627\",\"seqNo\": \"123456\",\"orderNo\": \"order123456\", \"orderAmt\": \"1500\"}"; + String value="{\"data\":{\"companyCode\":\"CN000001\",\"purpose\":\"加入中国光伏行业协会会员单位,获得行业技术支持,2025年会员单位缴纳标准为1万元/年,汇款时需注明“会费”字样,并在备注中注明汇款单位;缴纳会费后,需有付款凭证,将付款凭证发送至协会邮箱,并填写开票信息等内容\",\"recAccountNum\":\"8110701012801540483\",\"recAccountName\":\"\",\"payAccountNum\":\"8110701012601540892\",\"fundType\":\"0001\",\"transAmount\":\"9.90\",\"documentNo\":\"DG202503240321\",\"recBankCode\":\"\",\"settleAccountType\":\"CASH_TRANSFER\",\"sourceFlowNumber\":\"-3854827654841675885_1539\",\"submitUser\":\"user1\",\"digest\":\"加入中国光伏行业协会会员单位,获得行业技术支持,2025年会\",\"payChannel\":\"DIRECT\",\"currency\":\"CNY\",\"toPublic\":true,\"payAccountName\":\"\",\"payDate\":\"2025-03-26\"}}"; + System.out.println("明文:" + value); + //加密 + String encryptStr = encrypt(value, publicKey); + //String encryptStr="BAYF5RocTMvWHK4JLruoWNmvoBtSgEK6bHrXUIBcso3dx312VyI57p3bahg/qjcQufpa59XiMd8wA+cjyXmYm9+P4Y7DY5dZJQi6FdPZpL6awouRZMagdlceOL8yW5zDnx8tMqreUTAwkpvViwKn4DydKdV9EW0XuWkBwsN+4ZQftrY3FT/5Y/g8XtNSQMBu9PIwGWtPtpEtwO0HnaGCu7DGCSF+lo4PvYFmIlj0f2hQ1V1osdNIJ3IqUeZ0IZVFulaaOE72+vgAQN7UO0447kPq5iC8++oyXP7Bhf9kfU1gYOVXciIeAXoxIcjX+5DCB1q/FOmzqnOIBLRhSysqRXJa0ZvFgf1Ebpr6Fvz2Beim8SOEl3pXeLnLCJe91qsMuENLfNTOYnuESgSmdE1np3OJKcZwgeryUIt+WP7L28aJ1zNisWhB+TJaGtvWR++zY2fmMeZmwtJEz25jdmN1OWSuxgjXpI7fhIYoDnKQBQJKM5FQ+cWqJJozUMOlKG8Xhf5Lv1NjZOiNM7klp/Uprb3kUQj466ArWvsLzsoT1OOTOyBRJTbhKPSKi39ekcz4/ELuGSNB8Oz0pNg1tjbuyIcbiy2WCQI9/feiE+LyGgQPiUsaNxte10bYZuJJV33UWrBiezL0eA5pSm55zd98ToCX0QnJJq5OGW/8t8O/7+fZcSq5EFXSfS+3An8+EMLnBvI1ycjb9Q7PFVP4PmZ/VFqWJDNyi98iekWV09DVCtnCHqA5ZqEcPgZCeY40ZN5EQARvy11RWzs14Gj8JninjFbV6qtgB1Cadmy/piZS/SZdEvSnBfe04vTOElgG5GhraLrfMyVhnsmLeoqzLXmMT62uktPXKITjvFgr2NfdzbY5TkkDVMJu1b3VhZxOZENnJ4kW6nctYO0I7Z+xpVuMUnNOVxQBSJch2AdwIuyEL3odJSOXUt+RZIXhGNhyKw2Y8PvPQx+hOoCUyKyaUIS8G6RK/LI+TyMZTMsCUXYBEIxP10slFPp/4PCa7RPLhvdHsgik+cWNJj45tJRql9YXF1moWIaAUmLeu/ytCho1arA3V6/NjjckWI9mnqs41U2FAchJXdU2wNdIDB4AgGSsZs+o9sv+chgMgITV8/8xPNchu6g1UuczEXZRPFtKsVXlAJkNzQgBrMwCatby1S4CJFc5S/AJNIYipwLGW+M+3rh2+Ay+3HqDv7+7LUn+MTxgFfiosyK3hxkekY/eUg=="; + System.out.println("加密结果:" + encryptStr); + + //解密 + String decryptStr = decrypt(encryptStr, privateKey); + System.out.println("解密结果:" + decryptStr); + } + + /** + * 判断字符串是否为空 + * + * @param str 字符串 + * @return true/false + */ + public static boolean isBlank(String str) { + int strLen; + if (str != null && (strLen = str.length()) != 0) { + for (int i = 0; i < strLen; ++i) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + } + return true; + } + + + public static String substring(final String str, int start) { + if (str == null) { + return null; + } + // handle negatives, which means last n characters + if (start < 0) { + start = str.length() + start; // remember start is negative + } + if (start < 0) { + start = 0; + } + if (start > str.length()) { + return ""; + } + return str.substring(start); + } + + public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) { + if (str == null || searchStr == null) { + return -1; + } + return lastIndexOfIgnoreCase(str, searchStr, str.length()); + } + + public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) { + if (str == null || searchStr == null) { + return -1; + } + if (startPos > str.length() - searchStr.length()) { + startPos = str.length() - searchStr.length(); + } + if (startPos < 0) { + return -1; + } + if (searchStr.length() == 0) { + return startPos; + } + for (int i = startPos; i >= 0; i--) { + if (regionMatches(str, i, searchStr, searchStr.length())) { + return i; + } + } + return -1; + } + + static boolean regionMatches(final CharSequence cs, final int thisStart, + final CharSequence substring, final int length) { + if (cs instanceof String && substring instanceof String) { + return ((String) cs).regionMatches(true, thisStart, (String) substring, 0, length); + } + int index1 = thisStart; + int index2 = 0; + int tmpLen = length; + // Extract these first so we detect NPEs the same as the java.lang.String version + final int srcLen = cs.length() - thisStart; + final int otherLen = substring.length(); + // Check for invalid parameters + if (thisStart < 0 || length < 0) { + return false; + } + // Check that the regions are long enough + if (srcLen < length || otherLen < length) { + return false; + } + while (tmpLen-- > 0) { + final char c1 = cs.charAt(index1++); + final char c2 = substring.charAt(index2++); + + if (c1 == c2) { + continue; + } + // The same check as in String.regionMatches(): + if (Character.toUpperCase(c1) != Character.toUpperCase(c2) + && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { + return false; + } + } + return true; + } +}