在web应用中,有时候会需要将前后端交互的数据进行加密。我最近在做一个项目中,就想把后端的文件流加密后传给前端,但不知道为什么前端总是无法正常解密。

PS:我采用的加密算法是AES。

在摸索的过程中,由于看了Base64相关的东西,我关注到了字符的编码问题。

为了排查问题,我在本地进行测试。

 

先将明文加密后输出到控制台:

aes解密javascript aes解密中文乱码_字符串

aes解密javascript aes解密中文乱码_aes解密javascript_02

 

然后复制控制台的密文,进行解码,发现报错:

aes解密javascript aes解密中文乱码_字节数组_03

aes解密javascript aes解密中文乱码_字符串_04

 

不应该啊,输出密文乱码的原因是密文二进制流经过UTF8解码后,有些字(即2个字节)在Unicode字符集中对应的字符就是这些奇怪的字符,我复制这串乱码字符串,底层应该是复制了二进制流才对,这样粘贴到编辑器中,java代码运行的时候,读取到的也应该是二进制流才对。为了验证不是从控制台复制粘贴到编辑器的原因,我直接通过代码来模拟这一过程:

aes解密javascript aes解密中文乱码_二进制流_05

aes解密javascript aes解密中文乱码_字节数组_06

 

令人震惊的结果,竟然是false!但问题是出在AES加密上还是出在字符串和字节数组的相互转换上还不能明确,我继续写了一个测试代码,这次用不经过AES加密的字节数组。

aes解密javascript aes解密中文乱码_字节数组_07

aes解密javascript aes解密中文乱码_字节数组_08

 

这下知道问题出在哪里了,是AES加密后的字节数组出了问题。但其实AES加密只是引发该问题的原因之一,另一个原因和utf-8编码规则有关。问题是这样出现的:

 

byte[] buffer = "hello world".getBytes()这串代码,得到了一个对字符串"hello world"进行utf-8编码后的字节数组。此时直接用String out = new String(buffer)是可以得到字符串"hello world"的,因为此时的buffer数组是符合utf-8编码规则的。但是,用AES对buffer进行加密后,字节流发生了变化,已经不符合utf-8的编码规则了,这时候再使用String out = new String(buffer),buffer中有些字节utf-8是不认识的,无法正常解码,在java中,就会将这些无法正常解码的字节用�替代,正是因为这个原因,才将底层的数据流改变了,从而导致了加密后的buffer与buffer1的内容不一样了。

 

我们可以用iso-8859-1来代替utf-8,前者是以单字节作为编码单元,而后者则是以变长的多字节作为编码单元,对于一串未知的字节流,iso-8859-1是可以正常解码的,但utf-8就不一定了,utf-8解码对字节流的组织有一定的要求(因为utf-8是变长字节编码,因此在设计编码规则的时候对字节的结构有一定的要求,不符合该要求的的字节,utf-8就无法正确解码)。

aes解密javascript aes解密中文乱码_二进制流_09

aes解密javascript aes解密中文乱码_字符串_10