记录一次字符编码引起的BUG

Yunxiao 2022年11月13日 324次浏览

前言

最近在给客户部署一个项目的时候,遇到一个很诡异的BUG,是一个把系统中登录用户的信息通过AES加密的方式传递给第三方系统,然后第三方系统解密再登录的场景。第三方系统反馈解密后的用户名中文有部分乱码。我按照提供的用户Id本地测试,不论咋测都没有乱码,于是只能加日志排查。

排查过程

加了日志后,拿到服务器传递给第三方的密文,再进行解密,发现内容确实有乱码。同样的代码,为什么本机就没有乱码呢。于是想到了可能是系统默认的编码不一样,我本机的系统是win10,服务器是windows server 2012。
关键代码如下:

    public static String aesEncrypt(String content, byte[] keyBytes, byte[] iv){

        try{
            SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
            byte[] result=cipher.doFinal(content.getBytes("UTF-8"));
            return new String(Base64.encodeBase64(result),"UTF-8");
        }catch (Exception e) {
            System.out.println("exception:"+e.toString());
        }
        return null;
    }

涉及到编码的方法一共有两处,一个是String.getBytes()方法,一个是new String()方法,这个两个方法都是可以指定编码的,而我初始的AES加解密的代码都是从我们的线上项目复制过来的,是经过验证的,所以最开始也没向这方面想,后来复盘才觉得我们的线上服务器的操作系统肯定是指定了编码是UTF-8,所以没问题。
为了尽快修复这个BUG,我把AES的工具类里面的这两个方法的编码都强制指定为了UTF-8,打包上线,果然就正常了。

复盘

问题虽然是解决了,但还是要具体排查下原因。点击JDK的这两个方法源码,都到了Stringcoding这同一类,只不过String.getBytes()用的里面的encode方法,new String()用的里面的decode方法,这两个方法的第一行都是去获取默认编码。代码就是下面这个。

String csn = Charset.defaultCharset().name();
    public static Charset defaultCharset() {
        if (defaultCharset == null) {
            synchronized (Charset.class) {
                String csn = AccessController.doPrivileged(
                    new GetPropertyAction("file.encoding"));
                Charset cs = lookup(csn);
                if (cs != null)
                    defaultCharset = cs;
                else
                    defaultCharset = forName("UTF-8");
            }
        }
        return defaultCharset;
    }

咋一看好像如果没有指定file.encoding就会默认使用UTF-8,仔细一看其实最外层的defaultCharset肯定不是空的,JDK的注释如下:

Returns the default charset of this Java virtual machine.
The default charset is determined during virtual-machine startup and typically depends upon the locale and charset of the underlying operating system.
Returns:
A charset object for the default charset
Since:
1.5

按照这个意思,默认编码通常取决于操作系统。一看windows server2012的默认编码就是GBK。
我又看了下我的win10默认编码,也是GBK,那为啥本地复现不到呢?想了下,因为是IDEA指定了编码是UTF-8的缘故,一般我们在配置IDEA的时候都全局的配置了默认字符集。故而本地在IDEA调试代码的时候一直使用的就是UTF-8。