在获取到微信的用户昵称头像信息、手机号码信息,小程序为了保证数据安全提供了aes加密,后端想要保存这些信息就必须进行解密。
本文就记录下项目中用使用java开发语言对微信小程序前端的加密数据进行解密时碰到的问题。

问题复现

正常按照小程序推荐的流程开发,按照下图流程

  1. 每次进入小程序,前端将code传到后台,后端根据code、appid、appsecret获取用户的session_key和openId保存到数据库中。
  2. 用户授权获取头像信息后将 加密字符串+iv传到后台。
  3. 后台根据用户的openId先从数据库中查询到sessionKey,然后通过sessionKey解密获取到用户信息或手机号码信息。

在进行第三步解密的时候,发现会有解密失败的情况:

javax.crypto.BadPaddingException: pad block corrupted

由于官方的对小程序的aes解密没有提供java版本,从网上可以搜到很多java的解密方式,我也是参考的网上的这些解密方式,并且有很多人也遇到了类似的问题,搜索了很多帖子,包括小程序官方论坛都没有找到具体的解决方案。

问题解决

这时候重新捋一遍思路,需要确认两个事:

  1. 解密的代码有问题吗?
  2. 前端加密的数据是根据sessionKey加密的,后台只能用相同的sessionKey才能解密,不然肯定保存,所以需要确认是不是前端登录状态刷新了,后端用旧的sessionKey解密肯定是失败的。

第一点很容易排除,解密这种1+1=2的事,应该不会有有的时候成功,有的时候失败的情况。所以需要主要分析下第二点。

在查阅小程序文档的时候发现下面一句话:

微信小程序encrypteddata解密 java 微信小程序解密失败_java


在回调中调用 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;
    }

}