在获取到微信的用户昵称头像信息、手机号码信息,小程序为了保证数据安全提供了aes加密,后端想要保存这些信息就必须进行解密。
本文就记录下项目中用使用java开发语言对微信小程序前端的加密数据进行解密时碰到的问题。
问题复现
正常按照小程序推荐的流程开发,按照下图流程
- 每次进入小程序,前端将code传到后台,后端根据code、appid、appsecret获取用户的session_key和openId保存到数据库中。
- 用户授权获取头像信息后将 加密字符串+iv传到后台。
- 后台根据用户的openId先从数据库中查询到sessionKey,然后通过sessionKey解密获取到用户信息或手机号码信息。
在进行第三步解密的时候,发现会有解密失败的情况:
javax.crypto.BadPaddingException: pad block corrupted
由于官方的对小程序的aes解密没有提供java版本,从网上可以搜到很多java的解密方式,我也是参考的网上的这些解密方式,并且有很多人也遇到了类似的问题,搜索了很多帖子,包括小程序官方论坛都没有找到具体的解决方案。
问题解决
这时候重新捋一遍思路,需要确认两个事:
- 解密的代码有问题吗?
- 前端加密的数据是根据sessionKey加密的,后台只能用相同的sessionKey才能解密,不然肯定保存,所以需要确认是不是前端登录状态刷新了,后端用旧的sessionKey解密肯定是失败的。
第一点很容易排除,解密这种1+1=2的事,应该不会有有的时候成功,有的时候失败的情况。所以需要主要分析下第二点。
在查阅小程序文档的时候发现下面一句话:
在回调中调用 wx.login 登录,可能会刷新登录态。此时服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,导致解密失败。建议开发者提前进行 login;或者在回调中先使用 checkSession 进行登录态检查,避免 login 刷新登录态。
询问前台发现果然是因为前段在使用wx.login接口的时候是在某方法的回调中使用的。这就导致了虽然前端调用微信checkSession时,虽然登录状态没有刷新,但是在前端取code的时候,由于在回调中使用了wx.login导致登录状态已经刷新,所以后端用旧的sessionKey当然解不了用新的sessionKey加密的内容。 跟前端讨论后,明确了前端与后端交互的流程,问题解决,没有再发生解密失败的情况。
下面附上进行aes解密的java代码:
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.spec.InvalidParameterSpecException;
import java.util.Base64;
@Slf4j
public class WechatUtils {
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 解密
*
* @param sessionKey
* @param iv
* @param encryptedData
* @return
* @throws NoSuchPaddingException
* @throws NoSuchAlgorithmException
* @throws InvalidParameterSpecException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
public static String decryptWeChatData(String sessionKey, String iv, String encryptedData) {
final Base64.Decoder decoder = Base64.getMimeDecoder();
byte[] sessionKeyByte = decoder.decode(sessionKey);
byte[] encryptedDataByte = decoder.decode(encryptedData);
byte[] ivByte = decoder.decode(iv);
byte[] bytes;
try {
Key key = new SecretKeySpec(sessionKeyByte, "AES");
AlgorithmParameters algorithmParameters = null;
Cipher cipher = null;
algorithmParameters = AlgorithmParameters.getInstance("AES");
algorithmParameters.init(new IvParameterSpec(ivByte));
cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, key, algorithmParameters);
bytes = cipher.doFinal(encryptedDataByte);
} catch (InvalidParameterSpecException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
bytes = new byte[0];
}
String decryptString = new String(bytes, StandardCharsets.UTF_8);
log.info("微信解密后数据为:{}", decryptString);
return decryptString;
}
}