小程式開發者如需要獲取敏感資料,需要對介面返回的加密資料( encryptedData )進行對稱解密。 解密演算法如下:
- 對稱解密使用的演算法為 AES-128-CBC,資料採用PKCS#7填充。
- 對稱解密的目標密文為 Base64_Decode(encryptedData)。
- 對稱解密祕鑰 aeskey = Base64_Decode(session_key), aeskey 是16位元組。
- 對稱解密演算法初始向量 為Base64_Decode(iv),其中iv由資料介面返回。
這裡使用微信提供的 PKCS7Encoder
對小程式進行加密資料解密, 也提供了加密方法
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* 提供基於PKCS7演算法的加解密介面.
*/
public class PKCS7Encoder {
static Charset CHARSET = Charset.forName("utf-8");
static int BLOCK_SIZE = 32;
/**
* 獲得對明文進行補位填充的位元組.
*
* @param count 需要進行填充補位操作的明文位元組個數
* @return 補齊用的位元組陣列
*/
public static byte[] encode(int count) {
// 計算需要填充的位數
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
if (amountToPad == 0) {
amountToPad = BLOCK_SIZE;
}
// 獲得補位所用的字元
char padChr = chr(amountToPad);
String tmp = new String();
for (int index = 0; index < amountToPad; index++) {
tmp += padChr;
}
return tmp.getBytes(CHARSET);
}
/**
* 刪除解密後明文的補位字元
*
* @param decrypted 解密後的明文
* @return 刪除補位字元後的明文
*/
public static byte[] decode(byte[] decrypted) {
int pad = decrypted[decrypted.length - 1];
if (pad < 1 || pad > 32) {
pad = 0;
}
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}
/**
* 將數字轉化成ASCII碼對應的字元,用於對明文進行補碼
*
* @param a 需要轉化的數字
* @return 轉化得到的字元
*/
static char chr(int a) {
byte target = (byte) (a & 0xFF);
return (char) target;
}
}
複製程式碼
import java.util.ArrayList;
public class ByteGroup {
ArrayList<Byte> byteContainer = new ArrayList<Byte>();
public byte[] toBytes() {
byte[] bytes = new byte[byteContainer.size()];
for (int i = 0; i < byteContainer.size(); i++) {
bytes[i] = byteContainer.get(i);
}
return bytes;
}
public ByteGroup addBytes(byte[] bytes) {
for (byte b : bytes) {
byteContainer.add(b);
}
return this;
}
public int size() {
return byteContainer.size();
}
}
複製程式碼
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* @author yyb
* @since 2018-01-10
*/
public class WxCryptUtils {
/**
* 小程式 資料解密
*
* @param encryptData 加密資料
* @param iv 對稱解密演算法初始向量
* @param sessionKey 對稱解密祕鑰
* @return 解密資料
*/
public static String decrypt(String encryptData, String iv, String sessionKey) throws Exception {
AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("AES");
algorithmParameters.init(new IvParameterSpec(Base64.decodeBase64(iv)));
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(sessionKey), "AES"), algorithmParameters);
byte[] decode = PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptData)));
String decryptStr = new String(decode, StandardCharsets.UTF_8);
return decryptStr;
}
/**
* 資料加密
*
* @param data 需要加密的資料
* @param iv 對稱加密演算法初始向量
* @param sessionKey 對稱加密祕鑰
* @return 加密資料
*/
public static String encrypt(String data, String iv, String sessionKey) throws Exception {
AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("AES");
algorithmParameters.init(new IvParameterSpec(Base64.decodeBase64(iv)));
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(sessionKey), "AES"), algorithmParameters);
byte[] textBytes = json.getBytes(StandardCharsets.UTF_8);
ByteGroup byteGroup= new ByteGroup();
byteGroup.addBytes(textBytes);
byte[] padBytes = PKCS7Encoder.encode(byteGroup.size());
byteGroup.addBytes(padBytes);
byte[] encryptBytes = cipher.doFinal(byteGroup.toBytes());
return Base64.encodeBase64String(encryptBytes);
}
public static void main(String[] args) throws Exception {
// 微信 小程式的 測試資料
String encrypt = "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZM\n" +
" QmRzooG2xrDcvSnxIMXFufNstNGTyaGS\n" +
" 9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+\n" +
" 3hVbJSRgv+4lGOETKUQz6OYStslQ142d\n" +
" NCuabNPGBzlooOmB231qMM85d2/fV6Ch\n" +
" evvXvQP8Hkue1poOFtnEtpyxVLW1zAo6\n" +
" /1Xx1COxFvrc2d7UL/lmHInNlxuacJXw\n" +
" u0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn\n" +
" /Hz7saL8xz+W//FRAUid1OksQaQx4CMs\n" +
" 8LOddcQhULW4ucetDf96JcR3g0gfRK4P\n" +
" C7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB\n" +
" 6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns\n" +
" /8wR2SiRS7MNACwTyrGvt9ts8p12PKFd\n" +
" lqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYV\n" +
" oKlaRv85IfVunYzO0IKXsyl7JCUjCpoG\n" +
" 20f0a04COwfneQAGGwd5oa+T8yO5hzuy\n" +
" Db/XcxxmK01EpqOyuxINew==";
String sessionKey = "tiihtNczf5v6AKRyjwEUhQ==";
String iv = "r7BXXKkLb8qrSNn05n0qiA==";
String decrypt = WxCryptUtils.decrypt(encrypt, iv, sessionKey);
System.out.println(decrypt);
}
}
複製程式碼
最後的結果:
{
"country": "CN",
"unionId": "ocMvos6NjeKLIBqg5Mr9QjxrP1FA",
"gender": 1,
"province": "Guangdong",
"city": "Guangzhou",
"avatarUrl": "http://wx.qlogo.cn/mmopen/vi_32/aSKcBBPpibyKNicHNTMM0qJVh8Kjgiak2AHWr8MHM4WgMEm7GFhsf8OYrySdbvAMvTsw3mo8ibKicsnfN5pRjl1p8HQ/0",
"openId": "oGZUI0egBJY1zhBYw2KhdUfwVJJE",
"nickName": "Band",
"language": "zh_CN",
"watermark": {
"appid": "wx4f4bc4dec97d474b",
"timestamp": 1477314187
}
}
複製程式碼