java byte array 转String在转回byte array不相等
背景
最近在搞微软的NBFS协议,这个协议实际上也是基于WebService,只不过对xml进行了压缩,按照他自己的编码规则进行压缩
网上搜罗一圈后发现有个大佬写好的burp的NBFS插件WCF-Binary-SOAP-Plug-In
这个插件会将传入的经过base64编码的xml转换成NBFS协议的base64编码的字符串
测试那边要使用jmeter对这个NBFS接口性能测试
基本思路:
- 新建
http request输入原始的xml - 搞一个
PreProcessor,调用大佬写的NBFS.exe获取压缩后的XMLbase64编码的字符串
PreProcessor 代码如下
1 | import org.apache.commons.codec.binary.Base64; |
这时候就会发现一个神奇的东西,接口会返回400错误
问题分析
先说明下NBFS编码的原理,为了压缩xml,NBFS会预先定义一些字典,比如:0x90代表Reason这个字符,
也就是说,经过NBFS.exe返回的Base64字符串经过Base64.decodeBase64(nbfsEncodeBase64Str)这个方法返回的byte数组中可能出现0x80,0xAA等字节
众所周知,java默认的字符串编码是UTF-8
另一个常识是:java字符串是由Char[]表示的,而Char是由unicode表示
ok,有了上面的已知条件,返回400的问题,就是解释为什么下面代码输出是false就行了
1 | byte[] byteArray = new byte[]{0x01, (byte)0x81, (byte)0xAA, 0x44, 0x45}; |
其实断点调试下,会发现其实revertByteArray这个数组其实是:[1, -17, -65, -67, -17, -65, -67, 68, 69]而原数组是:[1, -128, -86, 68, 69]
区别就是多出了6个byte,其中0x81,0xAA对应变成[-17,-65,-67],对应10进制的65533,对应的unicde就是:\uFFFD
接下来就分析一下为什么0x81为什么会变成[-17,-65,-67]这玩意就行了
debug大法
对new String(byteArr)这段代码开始debug,
就会发现最终会进入一个超长的方法:
sun.nio.cs.UTF_8.Decoder#decode:
1 | // da 就是字符串找保存的char[] |
上面代码翻译成人话就是:
循环byte数组里面的每一个byte
- 如果>=0,就是ASCII
- 如果不是,判断是否符合条件:
- 1 byte, 7 bits: 0xxxxxxx
- 2 bytes, 11 bits: 110xxxxx 10xxxxxx
- 3 bytes, 16 bits: 1110xxxx 10xxxxxx 10xxxxxx
- 4 bytes, 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
- 如果都不符合,就返回
replacement()第0个char(打断点看,这个字符其实就是main类启动时创建的,其实这就是’65533’也就是\uFFFD)
至此,真相大白
0x80->0b100000000x88->0b10010000
都不在上面的范围,所以他们最终的char最终都会变成65533(\uFFFD)
其实上面的规则就是UTF-8规则,参考维基百科:
在ASCII码的范围,用一个字节表示,超出ASCII码的范围就用字节表示,这就形成了我们上面看到的UTF-8的表示方法,这样的好处是当UNICODE文件中只有ASCII码时,存储的文件都为一个字节,所以就是普通的ASCII文件无异,读取的时候也是如此,所以能与以前的ASCII文件兼容。
大于ASCII码的,就会由上面的第一字节的前几位表示该unicode字符的长度,比如110xxxxx前三位的二进制表示告诉我们这是个2BYTE的UNICODE字符;1110xxxx是个三位的UNICODE字符,依此类推;xxx的位置由字符编码数的二进制表示的位填入。越靠右的x具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头”1”的数目就是整个串中字节的数目。
太长不看
一句话总结:因为类似0x80,0x88等字节不在utf-8编码范围内,所以会返回一个默认字符,\uFFFD(65533),这个字符占3个字节,所以就造成的两个byte数组不相等
解决方法
解决方法很简单:
找一个编码覆盖0x00 到 0xff,并且只用一个字节编码的编码格式就行,
也就是ISO-8859-1
所以下面代码输出就是true
1 | byte[] byteArray = new byte[]{0x01, (byte)0x81, (byte)0xAA, 0x44, 0x45}; |
java byte array 转String在转回byte array不相等
https://fingergohappy.github.io/2023/09/21/java-byte-convert-string-not-equal/

