pom 依賴
非必須,hutool 是為了使用 AES 工具類,bcprov 是為了使用 PKCS7Padding,都可以自己實現,這里為了方便。
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool-all.version}</version> </dependency> <dependency><!--AES/CBC/PKCS7Padding--> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15to18</artifactId> <version>${bcprov-jdk15to18.version}</version> </dependency>
工具類
import cn.hutool.json.JSONUtil; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.*; import java.security.spec.InvalidParameterSpecException; public class WeChatUtil { static { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); // 初始化密鑰 } /** * 解密數據 */ public static String decrypt(String appId, String sessionKey, String encryptedData, String iv) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidParameterSpecException, BadPaddingException, InvalidKeyException { // AES aes = new AES("CBC", "PKCS7Padding", Base64.decode(sessionKey), Base64.decode(iv)); // byte[] resultByte = aes.decrypt(Base64.decode(encryptedData)); byte[] resultByte = wxDecrypt(encryptedData, sessionKey, iv); String result = new String(resultByte, StandardCharsets.UTF_8); // 是否與當前 appid 相同 if (!appId.equals(JSONUtil.parseObj(result).getJSONObject("watermark").getStr("appid"))) { result = ""; } return result; } public static final String KEY_NAME = "AES"; // 算法名 // 加解密算法/模式/填充方式,ECB 模式只用密鑰即可對數據進行加密解密,CBC 模式需要添加一個 iv public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding"; /** * 接口如果涉及敏感數據(如 wx.getUserInfo 當中的 openId 和 unionId),接口的明文內容將不包含這些敏感數據。 * 開發者如需要獲取敏感數據,需要對接口返回的加密數據(encryptedData) 進行對稱解密。 解密算法如下: * * 對稱解密使用的算法為 AES-128-CBC,數據采用 PKCS#7 填充。 * * 對稱解密的目標密文為 Base64_Decode(encryptedData)。 * * 對稱解密秘鑰 aeskey = Base64_Decode(session_key), aeskey 是16字節。 * * 對稱解密算法初始向量 為 Base64_Decode(iv),其中 iv 由數據接口返回。 * * @param encrypted 目標密文 * @param sessionKey 會話ID * @param iv 加密算法的初始向量 */ public static byte[] wxDecrypt(String encrypted, String sessionKey, String iv) throws NoSuchAlgorithmException, InvalidParameterSpecException, NoSuchPaddingException, BadPaddingException, InvalidKeyException, IllegalBlockSizeException, InvalidAlgorithmParameterException { KeyGenerator.getInstance(KEY_NAME).init(128); // 生成 iv // iv 為一個 16 字節的數組,這里采用和 iOS 端一樣的構造方法,數據全為 0 // Arrays.fill(iv, (byte) 0x00); AlgorithmParameters ivj = AlgorithmParameters.getInstance(KEY_NAME); ivj.init(new IvParameterSpec(java.util.Base64.getDecoder().decode(iv))); // 生成解密 Key key = new SecretKeySpec(java.util.Base64.getDecoder().decode(sessionKey), KEY_NAME); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key, ivj); // 設置為解密模式 return cipher.doFinal(java.util.Base64.getDecoder().decode(encrypted)); } }
測試
public static void main(String[] args) { String appId = "wx4f4bc4dec97d474b"; String encryptedData = "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJl" + "AC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW" + "1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs" + "8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2" + "SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COw" + "fneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew=="; String sessionKey = "tiihtNczf5v6AKRyjwEUhQ=="; String iv = "r7BXXKkLb8qrSNn05n0qiA=="; System.out.println(WeChatUtil.decrypt(appId, sessionKey, encryptedData, iv)); }
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html#加密數據解密算法