javax.crypto.BadPaddingException: pad block corrupted

最近做微信小程序获取用户绑定的手机号信息解密,试了很多方法。最终虽然没有完全解决,但是也达到我的极限了。有时会报错:javax.crypto.BadPaddingException: pad block corrupted。

  • 出现错误的详细描述

每次刚进入小程序登陆获取手机号时,会出现第一次解密失败,再试一次就成功的问题。如果连续登出,登入,就不会再出现揭秘失败的问题。但是如果停止操作过一会,登出后登入,又会出现第一次揭秘失败,再试一次就成功的问题。
网上说的,官方文档上注意点我都排除了。获取的加密密文是在前端调取wx.login()方法后,调用我后端的微信授权接口,获取用户的sessionkey,openId.然后才是前端调用的获取sessionkey加密的用户手机号接口,所以我可以保证每次sessionkey是最新的。不会过期。
并且我通过日志发现在sessionkey不变的情况下,第一次失败,第二次解密成功。

首先说一下解密的流程

微信为了安全,把解密的key,和加密的用户数据分成了两步,分别给了前台,后台。这样,如果不监听到两次请求,是无法解密的。具体步骤:
1: 前端调取微信获取code接口
2: 在通过code调用后台授权登陆接口,后台通过code换取用户的openid,sessionKey,unionid.并将这写信息保存到redis
3.前端通过button,经用户同意后获取到加密的用户信息,调用后台接口进行解密。

encryptedData:f7KBxq7XZ1SGBYAVNUPLqsBX6bdKLTTwlN+BvvKg1YY92eOxg0EisRRRlYYG2ZAbpDlCeWT1GoGYvk4nrc0brLPrwgIfSSDQo7QAq5iUFtXi7t/p3p/t8CrS28rJB9niTsrteS8g78tASaiDB1WIt34Qjx0TjtIIMjFVY/FXjRtltuve3ADPrefYOkBoU2TRCpXXvdBIxTFx6GyyzZb/pQ==,iv:PD89jqhSTSDCB3dA146Ffw==,sessionKey:xAunW5fXE1dEdMZYsx1AbA==

下面是解密后的信息

{
    "phoneNumber":"182****6271",
    "purePhoneNumber":"182****271",
    "countryCode":"86",
    "watermark":{
        "timestamp":1566963882,
        "appid":"wx7b****c3474687"
    }
}

4.后台通过解密算法。获取如上出参,并根据需求返回手机号等。

对于偶尔报错的解决方案(后面又加了一段,这个不是最终方案)

如果调用接口第一次出现了异常:javax.crypto.BadPaddingException: pad block corrupted,则返回页面一个可以判断的code,这样前端就可以提示用户再试一次,一般不会出现两次都报错的情况,两次至少一次能够成功。
如果不能接受上述方案,还有一种方案就是让前端在获取用户手机号这个button按钮中,先调用后台的微信授权接口两次,你没看错,调用两次。再去调用获取用户手机号,这是获取的密文是可以一遍过的。经验是这样的。具体原因不清楚。
解密的代码是我在网络的大海里掏出来了,真是不容易。亲测,并在使用。下面贴出来。再补上一嘴:下面的并没有pom的专门引入。如果实在导不进来,在用下面给的pom依赖。

        
        
            org.codehaus.xfire
            xfire-core
            1.2.6
        
        
            org.bouncycastle
            bcprov-jdk16
            1.46
        

解密代码

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Base64;
import com.alibaba.fastjson.JSONObject;

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.security.*;
import java.security.spec.InvalidParameterSpecException;


private String decryptNew(String encryptedData, String sessionKey, String iv) throws Exception {
        String result = "";
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
            // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            // 初始化
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                result = new String(resultByte, "UTF-8");
            }
        } catch (NoSuchAlgorithmException e) {
            LOGGER.error(e.getMessage(), e);
        } catch (NoSuchPaddingException e) {
            LOGGER.error(e.getMessage(), e);
        } catch (InvalidParameterSpecException e) {
            LOGGER.error(e.getMessage(), e);
        } catch (IllegalBlockSizeException e) {
            LOGGER.error(e.getMessage(), e);
        } catch (BadPaddingException e) {
            LOGGER.error(e.getMessage(), e);
        } catch (UnsupportedEncodingException e) {
            LOGGER.error(e.getMessage(), e);
        } catch (InvalidKeyException e) {
            LOGGER.error(e.getMessage(), e);
        } catch (InvalidAlgorithmParameterException e) {
            LOGGER.error(e.getMessage(), e);
        } catch (NoSuchProviderException e) {
            LOGGER.error(e.getMessage(), e);
        }
        return result;
    }

后续补充:

现在找到问题的解决方案。这个锅还真不是后端的问题。上面的工具类也是没问题的。出现偶尔报错的直接原因是前端同学调用的流程不太明白,导致在获取用户手机号的回调函数中,又调用了一遍login()方法,然后把加密数据传给我们来解密!
在回调中不应该调用登陆的方法,这样会导致刷新sessionkey,当我们用之前保存的sessionkey时,有小概率事件,甚至很大可能导致sessionkey过期。用旧的sessionkey解密显得sessionkey加密的数据,当然会报错。

你可能感兴趣的:(三方系统对接)