Browse Source

红衫健康加密工具类

xyx 1 tuần trước cách đây
mục cha
commit
5aca6865d4

+ 284 - 0
fs-common/src/main/java/com/fs/common/utils/RedwoodCryptoUtil.java

@@ -0,0 +1,284 @@
+package com.fs.common.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.*;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.TreeMap;
+
+/**
+ * 红杉健康开放平台加解密工具类
+ * 遵循文档规则:AES-256-CBC加密内容 + RSA加密AES密钥 + MD5签名
+ */
+public class RedwoodCryptoUtil {
+
+    // 加密算法配置(与文档一致)
+    private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
+    private static final String AES_KEY_ALGORITHM = "AES";
+    private static final int AES_KEY_LENGTH = 256;
+    private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
+    private static final String RSA_KEY_ALGORITHM = "RSA";
+    private static final String SIGN_ALGORITHM = "MD5";
+
+    // 配置参数(需根据实际环境替换)
+    private final String appSecret; // 平台提供的应用秘钥(作为AES初始化向量IV)
+    private final String platformPublicKey; // 红杉平台RSA公钥(加密AES密钥用)
+    private final String thirdPrivateKey; // 三方平台RSA私钥(解密响应密钥用)
+
+    /**
+     * 构造函数:初始化配置参数
+     *
+     * @param appSecret         平台应用秘钥(文档中appsecret)
+     * @param platformPublicKey 红杉平台RSA公钥(Base64编码)
+     * @param thirdPrivateKey   三方平台RSA私钥(Base64编码)
+     */
+    public RedwoodCryptoUtil(String appSecret, String platformPublicKey, String thirdPrivateKey) {
+        this.appSecret = appSecret;
+        this.platformPublicKey = platformPublicKey;
+        this.thirdPrivateKey = thirdPrivateKey;
+    }
+
+    /**
+     * 加密请求参数:生成接口所需的content、key、sign
+     *
+     * @param plainParam 明文请求参数(Map格式)
+     * @return 加密后的请求参数Map(包含content、key、sign)
+     * @throws Exception 加密异常
+     */
+    public Map<String, String> encryptRequest(Map<String, Object> plainParam) throws Exception {
+        // 1. 参数按ASCII码排序并转为JSON字符串(不转义Unicode)
+        Map<String, Object> sortedMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+        sortedMap.putAll(plainParam);
+        String contentStr = new String(
+                JSON.toJSONBytes(sortedMap, SerializerFeature.BrowserCompatible)
+        );
+
+        // 2. 生成32位随机AES密钥(randStr)
+        String aesKey = generateRandomAesKey();
+
+        // 3. AES-256-CBC加密contentStr(IV为appSecret)
+        String encryptedContent = aesEncrypt(contentStr, aesKey, appSecret);
+
+        // 4. MD5生成签名(对原始contentStr)
+        String sign = generateSign(contentStr);
+
+        // 5. 用平台公钥加密AES密钥(RSA-PKCS1-SHA256)
+        String encryptedAesKey = rsaEncrypt(aesKey, platformPublicKey);
+
+        // 6. 返回加密结果(Base64编码后)
+        Map<String, String> encryptedParam = new HashMap<>();
+        encryptedParam.put("content", Base64.encodeBase64String(encryptedContent.getBytes()));
+        encryptedParam.put("key", Base64.encodeBase64String(encryptedAesKey.getBytes()));
+        encryptedParam.put("sign", sign);
+        return encryptedParam;
+    }
+
+    /**
+     * 解密响应参数:从content、key、sign中还原明文
+     *
+     * @param encryptedResponse 加密响应参数(包含content、key、sign)
+     * @return 明文响应结果(Map格式)
+     * @throws Exception 解密异常
+     */
+    public Map<String, Object> decryptResponse(Map<String, String> encryptedResponse) throws Exception {
+        // 1. 提取加密字段并Base64解码
+        String encryptedContent = new String(Base64.decodeBase64(encryptedResponse.get("content")));
+        String encryptedAesKey = new String(Base64.decodeBase64(encryptedResponse.get("key")));
+        String sign = encryptedResponse.get("sign");
+
+        // 2. 用三方私钥解密AES密钥
+        String aesKey = rsaDecrypt(encryptedAesKey, thirdPrivateKey);
+
+        // 3. AES-256-CBC解密content(IV为appSecret)
+        String contentStr = aesDecrypt(encryptedContent, aesKey, appSecret);
+
+        // 4. 验证签名(防止数据篡改)
+        if (!verifySign(contentStr, sign)) {
+            throw new SecurityException("签名验证失败,数据可能被篡改");
+        }
+
+        // 5. 解析JSON为Map返回
+        return JSON.parseObject(contentStr, Map.class);
+    }
+
+    /**
+     * AES加密(AES-256-CBC)
+     */
+    private String aesEncrypt(String content, String key, String iv) throws Exception {
+        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), AES_KEY_ALGORITHM);
+        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
+        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
+        byte[] encryptedBytes = cipher.doFinal(content.getBytes("UTF-8"));
+        return Base64.encodeBase64String(encryptedBytes);
+    }
+
+    /**
+     * AES解密(AES-256-CBC)
+     */
+    private String aesDecrypt(String encryptedContent, String key, String iv) throws Exception {
+        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), AES_KEY_ALGORITHM);
+        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
+        Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
+        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
+        byte[] decryptedBytes = cipher.doFinal(Base64.decodeBase64(encryptedContent));
+        return new String(decryptedBytes, "UTF-8");
+    }
+
+    /**
+     * RSA加密(用公钥加密AES密钥)
+     */
+    private String rsaEncrypt(String content, String publicKeyStr) throws Exception {
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyStr));
+        PublicKey publicKey = KeyFactory.getInstance(RSA_KEY_ALGORITHM).generatePublic(keySpec);
+        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+        byte[] encryptedBytes = cipher.doFinal(content.getBytes("UTF-8"));
+        return Base64.encodeBase64String(encryptedBytes);
+    }
+
+    /**
+     * RSA解密(用私钥解密AES密钥)
+     */
+    private String rsaDecrypt(String encryptedContent, String privateKeyStr) throws Exception {
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyStr));
+        PrivateKey privateKey = KeyFactory.getInstance(RSA_KEY_ALGORITHM).generatePrivate(keySpec);
+        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
+        cipher.init(Cipher.DECRYPT_MODE, privateKey);
+        byte[] decryptedBytes = cipher.doFinal(Base64.decodeBase64(encryptedContent));
+        return new String(decryptedBytes, "UTF-8");
+    }
+
+    /**
+     * 生成MD5签名
+     */
+    private String generateSign(String content) throws Exception {
+        MessageDigest md5 = MessageDigest.getInstance(SIGN_ALGORITHM);
+        byte[] signBytes = md5.digest(content.getBytes("UTF-8"));
+        return bytesToHex(signBytes);
+    }
+
+    /**
+     * 验证MD5签名
+     */
+    private boolean verifySign(String content, String sign) throws Exception {
+        String generatedSign = generateSign(content);
+        return generatedSign.equalsIgnoreCase(sign);
+    }
+
+    /**
+     * 生成32位随机AES密钥(字母+数字)
+     */
+    private String generateRandomAesKey() {
+        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        StringBuilder sb = new StringBuilder(32);
+        Random random = new SecureRandom();
+        for (int i = 0; i < 32; i++) {
+            sb.append(chars.charAt(random.nextInt(chars.length())));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 字节数组转16进制字符串
+     */
+    private String bytesToHex(byte[] bytes) {
+        StringBuilder hex = new StringBuilder();
+        for (byte b : bytes) {
+            String hexStr = Integer.toHexString(b & 0xFF);
+            if (hexStr.length() == 1) {
+                hex.append("0");
+            }
+            hex.append(hexStr);
+        }
+        return hex.toString();
+    }
+
+    /**
+     * 生成RSA密钥对,用于platformPublicKey和thirdPrivateKey
+     */
+    public static void generateRSAKeyPair() throws Exception {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(2048); // 使用2048位密钥长度
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+        PublicKey publicKey = keyPair.getPublic();
+        PrivateKey privateKey = keyPair.getPrivate();
+
+        // 获取Base64编码的公钥和私钥
+        String publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());
+        String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());
+
+        System.out.println("公钥 (platformPublicKey):");
+        System.out.println(wrapPublicKey(publicKeyStr));
+        System.out.println();
+        System.out.println("私钥 (thirdPrivateKey):");
+        System.out.println(privateKeyStr);
+    }
+
+    /**
+     * 将公钥包装成PEM格式
+     */
+    private static String wrapPublicKey(String publicKey) {
+        return "-----BEGIN PUBLIC KEY-----\n" +
+                splitLines(publicKey) +
+                "\n-----END PUBLIC KEY-----";
+    }
+
+    /**
+     * 将长Base64字符串按64字符分行
+     */
+    private static String splitLines(String base64Str) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < base64Str.length(); i += 64) {
+            if (sb.length() > 0) sb.append("\n");
+            sb.append(base64Str.substring(i, Math.min(i + 64, base64Str.length())));
+        }
+        return sb.toString();
+    }
+
+    // ------------------------------ 测试示例 ------------------------------
+    public static void main(String[] args) throws Exception {
+        // 1. 配置参数(替换为文档中的测试参数或实际环境参数)
+        String appSecret = "uuRKVwbfSutCpFTv"; // 文档中测试appsecret
+        String platformPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk2oCpi9eSMqcmqO3rrMN\n" +
+                "b9fqEFMckcRkU520PXyxFalzA2mfLNgNPe8TB9mBSqn7xPxLBBBOSaFwEAeAAs4E\n" +
+                "AojMVilbfrTXJh3ucyKlGvTpX3lMKgJaYG7asEzKVbw5V4j8oTBtNh3s20jP7zBm\n" +
+                "Y61opsLTIW0qiuvMxezJS8wHc6ajY99RHWBASd3AGozsc3cFLSgIUyh2BhcbB2l7\n" +
+                "V8UyDQj7+QI1BwNpkvGxYhP0DF4Yu7buaaf61gygkeeOZ/8JOjcDWBfVB6Q6wEja\n" +
+                "os0E9U+rYXN6v90va1wrRp+zRev4sMBWesvwc6uzcuYO/RfSJ19FDMUreCScm8tk\n" +
+                "PQIDAQAB"; // 文档中测试红杉密钥-公钥
+        String thirdPrivateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCTagKmL15Iypyao7eusw1v1+oQUxyRxGRTnbQ9fLEVqXMDaZ8s2A097xMH2YFKqfvE/EsEEE5JoXAQB4ACzgQCiMxWKVt+tNcmHe5zIqUa9OlfeUwqAlpgbtqwTMpVvDlXiPyhMG02HezbSM/vMGZjrWimwtMhbSqK68zF7MlLzAdzpqNj31EdYEBJ3cAajOxzdwUtKAhTKHYGFxsHaXtXxTINCPv5AjUHA2mS8bFiE/QMXhi7tu5pp/rWDKCR545n/wk6NwNYF9UHpDrASNqizQT1T6thc3q/3S9rXCtGn7NF6/iwwFZ6y/Bzq7Ny5g79F9InX0UMxSt4JJyby2Q9AgMBAAECggEAEqfp6eo4vnGV3CQ4DM3wN2VV4/cAuJnoMITW2Kk9KAan3ZiyYlR9aIcnG2k1aaOVj1p2i+8cWUkrC3xHgRNdgoyZf5YAVErCp7pGASAzUPQJzOFm+DIQCgA9gO5W9P67Kw7VGfks+RpUbXQLjLPNYXQCuIgTfDl6ltY8the/ae4Y/a4dyhBhQ+Q/sygACk/Woiq7B78pc/qCaeMyZyik42aVuC7ZMuIgzERSOBFDrerI/upBWmLE4faeUk4h/PE1Hrj+H2RJ9aM5j4Idn/AJ4H4r8TjzT5m+ldQBfErs37HAVbyP1lQJjmO+VBBvqFchGNDtU1yGIRpxfU5mJI5dQQKBgQDRFPe+IKsheKNF2z0EUke/hU+XIDNc3Qwvp+2ok78G3LiKLRM61II/k+N0PLmJii0JEjqF0figj83Cyr4nnbhymOKIIplftgv085E1ed6QH0m54AwJBCLLA6gHiHin1t21Xh7o38FF6pHQ6EnQjfnVCODkTKxQEihvV3oAv/UBeQKBgQC0fnDPZ29YcIdI2W6xteDFaBfWLTw9usHDu2NnZBPEa+E5Whvcnw6xCgYEI+yasGEt9wOG6tnMEaxIMHRtn7LXaa7nrYxvqE5yTCLpjle3NM3KJRoCxZvOzJwlGWKFRPVpxBe1Sm0DB/IfTbBqzYCVkKQgpnqcpyI91TvxBL/r5QKBgA/bETaf75poNamUiLoNK1fA2lpRnNOMB+KNT56bJb91eaEw7eZmO0JrCrLD8CYYDnZDpaCEXeB/R1FgYq9KbLR0F6nPReZWPe3jkr2FcnVnigXIkeEVKTZQHqwDk3LW/pVEf/+VCGku8sPu+boRKkMXm0Z08hRYbCyVa7Em3YOxAoGAOomzfqC2TQGZ7reOHha1wnBjIrRjEEYsp5VzxMmBW7f9QMOHu8LeWe69SsR37Sd9LRIq06wBXRzyOit050TfFNwSvNLddC0q3AjzXbormqCGiaQEzpdWU/iqP6H/AOf/jADsC4EK3+vIy/w/VjQ2GsvhXzF/HKVcBp/Mo/t9Xz0CgYACKTI8f7z43g6MHy0+QOYBJ4BBDS8SzhLdNIXoUnc4O8kySTospxNVRzZGUMKJ4R8T7bpfR+50H6nCxBZo125eD4lcMHL3Dd/JwabKSer7Nzjt7+UMbgncQIs3m9FjtP1rvYuHvHI7RbxFH+hrYVWMKysWpbS9xtMThF9BosmufA=="; // 文档中测试三方密钥-私钥
+
+        // 2. 创建工具类实例
+        RedwoodCryptoUtil cryptoUtil = new RedwoodCryptoUtil(appSecret, platformPublicKey, thirdPrivateKey);
+
+        // 3. 加密请求参数示例(模拟购药问诊申请参数)
+        Map<String, Object> plainParam = new HashMap<>();
+        plainParam.put("prescription_type", 1);
+        plainParam.put("register_type", "picture");
+        plainParam.put("union_code", "TEST20250819001");
+        // 其他参数按接口要求补充...
+
+        Map<String, String> encryptedParam = cryptoUtil.encryptRequest(plainParam);
+        System.out.println("加密后的请求参数:" + encryptedParam);
+
+        // 4. 解密响应参数示例(模拟接口返回的加密数据)
+        Map<String, String> mockEncryptedResponse = new HashMap<>();
+        mockEncryptedResponse.put("content", "加密后的content");
+        mockEncryptedResponse.put("key", "加密后的key");
+        mockEncryptedResponse.put("sign", "加密后的sign");
+
+        Map<String, Object> plainResponse = cryptoUtil.decryptResponse(mockEncryptedResponse);
+        System.out.println("解密后的响应结果:" + plainResponse);
+    }
+}