1.背景介紹
微信小程序?qū)γ舾袛?shù)據(jù)加解密算法,以獲取微信的群ID(opengid)為例
微信端通過(guò)轉(zhuǎn)發(fā)獲取加密數(shù)據(jù)
微信端通過(guò)獲取微信群聊場(chǎng)景下的小程序啟動(dòng)信息
獲取的相關(guān)參數(shù)
但官方提供的加密數(shù)據(jù)加解密算法所支持的語(yǔ)言卻不包括Java
接口如果涉及敏感數(shù)據(jù)(如wx.getUserInfo當(dāng)中的 openId 和
unionId),接口的明文內(nèi)容將不包含這些敏感數(shù)據(jù)。開(kāi)發(fā)者如需要獲取敏感數(shù)據(jù),需要對(duì)接口返回的加密數(shù)據(jù)(encryptedData)
進(jìn)行對(duì)稱解密。 解密算法如下:對(duì)稱解密使用的算法為 AES-128-CBC,數(shù)據(jù)采用PKCS#7填充。 對(duì)稱解密的目標(biāo)密文為
Base64_Decode(encryptedData)。 對(duì)稱解密秘鑰 aeskey =
Base64_Decode(session_key), aeskey 是16字節(jié)。 對(duì)稱解密算法初始向量
為Base64_Decode(iv),其中 iv 由數(shù)據(jù)接口返回。
微信官方提供了多種編程語(yǔ)言的示例代碼((點(diǎn)擊下載)。每種語(yǔ)言類型的接口名字均一致。調(diào)用方式可以參照示例。另外,為了應(yīng)用能校驗(yàn)數(shù)據(jù)的有效性,會(huì)在敏感數(shù)據(jù)加上數(shù)據(jù)水印( watermark )。
2.解決方案①
Entity類
@ApiModel(value = "OpenGIdDTO", description = "獲取openGId的解密參數(shù)")
public class OpenGIdDTO {
@ApiModelProperty(value = "需要加密的數(shù)據(jù)")
private String encrypt;
@ApiModelProperty(value = "對(duì)稱加密秘鑰")
private String sessionKey;
@ApiModelProperty(value = "對(duì)稱加密算法初始向量")
private String iv;
public String getEncrypt() {
return encrypt;
}
public void setEncrypt(String encrypt) {
this.encrypt = encrypt;
}
public String getSessionKey() {
return sessionKey;
}
public void setSessionKey(String sessionKey) {
this.sessionKey = sessionKey;
}
public String getIv() {
return iv;
}
public void setIv(String iv) {
this.iv = iv;
}
}
Controller層
@ApiOperation(value = "獲取openGId")
@PostMapping(value = "/getOpenGId")
public JSONObject getOpenId(@RequestBody OpenGIdDTO openGIdDTO) throws Exception {
String decrypt = WXCore.decrypt(APPID, openGIdDTO.getEncrypt(), openGIdDTO.getSessionKey(), openGIdDTO.getIv());
JSONObject json = JSON.parseObject(decrypt);
return json;
}
工具類
public class WXCore {
private static final String WATERMARK = "watermark";
private static final String APPID = "openGId";
/**
* 解密數(shù)據(jù)
* @return
* @throws Exception
*/
public static String decrypt(String appId, String encryptedData, String sessionKey, String iv){
String result = "";
try {
AES aes = new AES();
byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));
if(null != resultByte && resultByte.length > 0){
result = new String(WxPKCS7Encoder.decode(resultByte));
JSONObject jsonObject = JSONObject.parseObject(result);
String decryptAppid = jsonObject.getJSONObject(WATERMARK).getString("appid");
if(!appId.equals(decryptAppid)){
result = "";
}
}
} catch (Exception e) {
result = "";
e.printStackTrace();
}
return result;
}
}
public class AES {
public static boolean initialized = false;
/**
* AES解密
*
* @param content
* 密文
* @return
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
*/
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
initialize();
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
byte[] result = cipher.doFinal(content);
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void initialize() {
if (initialized)
return;
Security.addProvider(new BouncyCastleProvider());
initialized = true;
}
// 生成iv
public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv));
return params;
}
}
3.解決方案②
Entity類一致
Controller層
@ApiOperation(value = "解密返參信息")
@PostMapping(value = "/getMess")
@UserAnnotation()
public JSONObject getMess(@RequestBody OpenGIdDTO openGIdDTO) throws Exception {
String decrypt = WxCryptUtils.decrypt(openGIdDTO.getEncrypt(), openGIdDTO.getIv(), openGIdDTO.getSessionKey());
JSONObject json = JSON.parseObject(decrypt);
return json;
}
工具類文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-493499.html
public class WxCryptUtils {
/**
* 小程序 數(shù)據(jù)解密
*
* @param encryptData 加密數(shù)據(jù)
* @param iv 對(duì)稱解密算法初始向量
* @param sessionKey 對(duì)稱解密秘鑰
* @return 解密數(shù)據(jù)
*/
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;
}
/**
* 數(shù)據(jù)加密
*
* @param data 需要加密的數(shù)據(jù)
* @param iv 對(duì)稱加密算法初始向量
* @param sessionKey 對(duì)稱加密秘鑰
* @return 加密數(shù)據(jù)
*/
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 = data.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 class PKCS7Encoder {
static Charset CHARSET = Charset.forName("utf-8");
static int BLOCK_SIZE = 32;
/**
* 獲得對(duì)明文進(jìn)行補(bǔ)位填充的字節(jié).
*
* @param count 需要進(jìn)行填充補(bǔ)位操作的明文字節(jié)個(gè)數(shù)
* @return 補(bǔ)齊用的字節(jié)數(shù)組
*/
public static byte[] encode(int count) {
// 計(jì)算需要填充的位數(shù)
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
if (amountToPad == 0) {
amountToPad = BLOCK_SIZE;
}
// 獲得補(bǔ)位所用的字符
char padChr = chr(amountToPad);
String tmp = new String();
for (int index = 0; index < amountToPad; index++) {
tmp += padChr;
}
return tmp.getBytes(CHARSET);
}
/**
* 刪除解密后明文的補(bǔ)位字符
*
* @param decrypted 解密后的明文
* @return 刪除補(bǔ)位字符后的明文
*/
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);
}
/**
* 將數(shù)字轉(zhuǎn)化成ASCII碼對(duì)應(yīng)的字符,用于對(duì)明文進(jìn)行補(bǔ)碼
*
* @param a 需要轉(zhuǎn)化的數(shù)字
* @return 轉(zhuǎn)化得到的字符
*/
static char chr(int a) {
byte target = (byte) (a & 0xFF);
return (char) target;
}
}
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();
}
}
4.實(shí)際使用中出現(xiàn)’javax.crypto.BadPaddingException: pad block corrupted’報(bào)錯(cuò)
出現(xiàn)原因:前端在調(diào)用取用戶手機(jī)號(hào)的回調(diào)函數(shù)中,又調(diào)用了一遍login()方法,然后把加密數(shù)據(jù)傳給我們來(lái)解密,在回調(diào)中不應(yīng)該調(diào)用登陸的方法,這樣會(huì)導(dǎo)致刷新sessionkey,當(dāng)我們用之前保存的sessionkey時(shí),可能導(dǎo)致sessionkey過(guò)期。用舊的sessionkey解密顯sessionkey加密的數(shù)據(jù),當(dāng)然會(huì)報(bào)錯(cuò)。
解決方案:前端不要調(diào)用2次login()方法文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-493499.html
到了這里,關(guān)于微信小程序?qū)γ舾袛?shù)據(jù)加解密算法(Java)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!