将字节数组转换为字符串,然后再转回字节数组时,为什么长度会不同?

6
我有以下的Java代码:
byte[] signatureBytes = getSignature();

String signatureString = new String(signatureBytes, "UTF8");
byte[] signatureStringBytes = signatureString.getBytes("UTF8");

System.out.println(signatureBytes.length == signatureStringBytes.length); // prints false

Q:我可能误解了这个问题,但我认为new String(byte[] bytes, String charset)String.getBytes(charset)是相反的操作?

Q:作为后续,以什么安全的方式传输byte[]数组作为字符串?

4个回答

10

不是每个 byte[] 都是有效的 UTF-8。默认情况下,无效的序列会被替换为固定字符,我认为这就是长度变化的原因。

尝试使用 Latin-1,应该不会出现这种情况,因为它是一个简单的编码,每个 byte[] 都有意义。

对于 Windows-1252 也不应该发生此问题。其中存在未定义的序列(实际上是未定义的字节),但所有字符都被编码在一个字节中。新的 byte[] 可能与原始值不同,但它们的长度必须相同。


那个可行。那么,将byte[]数组作为字符串传输的安全方法是什么? - John
3
org.apache.commons.codec.binary.Base64是传递任意数据的最佳伙伴,可以将数据转换为字符串 :) - Affe
没错,Base64是标准和最佳解决方案。只要你在Java中保持不变,Latin-1也可以很好地工作。 - maaartinus

5
我可能误解了这一点,但我认为new String(byte[] bytes, String charset)和String.getBytes(charset)是反向操作?
不一定。
如果输入的字节数组包含无效UTF-8序列,则初始转换可能会将它们转换为(例如)问号。然后第二个操作将这些字符转换为编码为UTF-8的'?'字符....与原始表示不同。
Unicode中确实有一些字符具有多个表示法。例如,带重音符号的字符可以是单个代码点,也可以是基本字符代码点和重音符号代码点。但是,在字节数组(包含有效的UTF-8)和字符串之间进行相互转换应该保留代码点顺序。它不执行任何“规范化”。
那么以什么方式安全地传输byte[]数组作为字符串呢?
最安全的选择是对字节数组进行base64编码。这样做的额外好处是:字符串中的字符将在转换为任何能够表示拉丁字母和数字的字符集/编码时得到保留。
另一种选择是使用Latin-1而不是UTF-8。但是:
- 如果错误地将数据误解为UTF-8,则存在损坏风险。 - 如果“string”然后嵌入到XML中,则此方法是不合法的。许多控制字符位于XML字符集之外,不能在XML文档中使用,即使编码为字符实体也不行。

谢谢!那么以什么安全的方式将byte[]数组作为字符串传输呢? - John

2

我想到了两种可能性。

首先,您的签名可能不是完全有效的UTF8格式。您不能随意将任何二进制数据“字符串化”。并非每个比特块都定义了一个合法字符。String构造函数将为二进制数据插入一些默认替换内容,这些内容在UTF8中实际上没有任何“意义”。这是一个不可逆转的过程。如果您想要“字符串化”某些任意二进制数据,您需要使用已经建立的方法,我建议使用org.apache.commons.codec.binary.Base64。

还有一些字符具有多个表示形式。例如,带有重音符号的字符可以编码为带有重音的字符或作为字符加上要组合的重音。在不同编码之间来回移动时,不能保证这是可逆的过程。


0

我想将数据存储为字符串到我的JSP页面,然后将该字符串作为参数发送到服务器端并转换为byte[]。这对我有用:

byte[]转换为字符串

String byteToString = Base64.getEncoder().encodeToString(byteContent);

将字符串转换为byte[]

byte[] stringToByte = Base64.getDecoder().decode(stringContent);

这将返回具有相同长度的确切 byte[]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接