【问题标题】:How to detect encoding mismatch如何检测编码不匹配
【发布时间】:2021-03-23 08:26:45
【问题描述】:

我有一堆旧的 AES 加密字符串,大致如下加密:

  1. 使用 ISO-8859-1 编码将字符串转换为字节
  2. 字节使用 AES 加密
  3. 结果转换为 BASE64 编码的字符数组

现在我想将新值的编码更改为 UTF8(例如,'€' 不适用于 ISO-8859-1)。这种意志 如果我尝试使用 UTF-8 编码解密旧的 ISO-8859-1 编码值,当然会导致问题:

org.junit.ComparisonFailure: expected:<!#[¤%&/()=?^*ÄÖÖÅ_:;>½§@${[]}<|'äöå-.,+´¨]'-Lorem ipsum dolor ...> but was:<!#[�%&/()=?^*����_:;>��@${[]}<|'���-.,+��]'-Lorem ipsum dolor ...>

我正在考虑为此创建一些自动编码回退。

所以主要问题是 检查解密的 char 数组中的“�”字符是否足以找出编码不匹配?什么是声明“�”的“正确”方式' 比较时的符号?

if (new String(utf8decryptedCharArray).contains("�")) {
    // Revert to doing the decrypting with ISO-8859-1
    decryptAsISO...
}

【问题讨论】:

  • 不可能知道,除非您有一组特定的字符不能出现在原始字符串上,您可以检查解密的字符串(当然在编码为 UTF8 时会出现)。我认为最好的解决方案是将元数据附加到要使用的实际编码中。
  • 实际上,您必须事先知道您正在处理的字节中使用了什么编码。或者你必须尝试一些编码检测,这是一项有点复杂的任务。
  • 也许可以尝试使用两种编码解密您的数据并分析两者的结果?
  • @m0skit0 提出的解决方案可行,但我宁愿解决这个问题而不必处理。
  • @Seelenvirtuose 如果我尝试两种编码,我应该从结果中分析什么?在某些情况下结果是相同的(等于),但在某些情况下结果是不同的(!等于)......仍然不知道哪个是正确的?我想出的唯一一件事就是找到那个“�”字符(当系统无法将数据流呈现为正确的符号时,它用于指示问题

标签: java encryption encoding


【解决方案1】:

解密时,你会得到原来的字节序列(你的步骤 1 的结果),然后你只能猜测这些字节是根据 ISO-8859-1 还是 UTF-8 编码来表示字符。

从一个字节序列中,没有办法清楚地说明它是如何被解释的。

一些想法:

  • 您可以迁移所有旧的加密字符串(解密、使用 ISO-8859-1 解码为字符串、使用 UTF-8 编码为字节数组、加密)。然后问题就永远解决了。
  • 您可以尝试解码两个版本的字节数组,看一个版本是否非法,或者两个版本是否相等,如果仍然模棱两可,则根据预期字符取概率较高的那个。我不建议这样做,因为它需要大量工作并且仍然存在一定的错误概率。
  • 对于新条目,您可以在字符串/字节序列前添加一些未出现在 ISO-8859-1 文本中的标记。例如。有些人按照惯例在 UTF-8 编码文件的开头添加字节顺序标记。尽管生成的字节 (EF BB BF) 在 ISO-8859-1 中并非严格非法(被读取为 ),但它们的可能性极小。然后,当您的解密字节以 EF BB BF 开头时,使用 UTF-8 解码为字符串,否则使用 ISO-8859-1。尽管如此,错误的概率仍然不为零。

如果可能的话,我会迁移现有条目。否则,你将不得不在你的代码库中永远使用“旧格式兼容性的东西”,并且仍然不能绝对保证正确的行为。

【讨论】:

  • 第一个建议非常好! +1
  • 完全同意迁移将是最好的解决方案,我必须更仔细地研究一下……唯一的问题是我有 300 多个不同的键和 3000 多个值,所以这样做有点可怕migration =/ @Andreas 也提供了一个很好的解决方案,我相信这与您在第二个选项中建议的有些相同。
  • @Jokkeri 对我来说,永远保持兼容性比编写临时迁移器并运行一次更可怕。我对这样的杂牌有一些个人经验。
  • @RalfKleberhoff 肯定同意这一点。由于只存在一种编码类型,因此进行迁移也简单得多。如果将值与 UTF8 和 ISO-8859-1 混合在一起,那就太糟糕了。我想我会为了未来牺牲现在的工作,并按照您提出的迁移进行
【解决方案2】:

将字节解码为文本时,不要依赖 字符来检测格式错误的输入。使用严格的解码器。这是一个辅助方法:

static String decodeStrict(byte[] bytes, Charset charset) throws CharacterCodingException {
    return charset.newDecoder()
            .onMalformedInput(CodingErrorAction.REPORT)
            .onUnmappableCharacter(CodingErrorAction.REPORT)
            .decode(ByteBuffer.wrap(bytes))
            .toString();
}

这里是对应的严格编码器辅助方法,以备不时之需:

static byte[] encodeStrict(String str, Charset charset) throws CharacterCodingException {
    ByteBuffer buf = charset.newEncoder()
            .onMalformedInput(CodingErrorAction.REPORT)
            .onUnmappableCharacter(CodingErrorAction.REPORT)
            .encode(CharBuffer.wrap(str));
    byte[] bytes = buf.array();
    if (bytes.length == buf.limit())
        return bytes;
    return Arrays.copyOfRange(bytes, 0, buf.limit());
}

由于 ISO-8859-1 允许所有字节,因此您不能使用它来检测格式错误的输入。然而,UTF-8 正在验证,因此它很可能检测到格式错误的输入。然而,它不是 100% 保证的,但这是我们能做到的最好的。

因此,尝试使用严格的 UTF-8 进行解码,如果失败则回退到 ISO-8859-1:

static String decode(byte[] bytes) {
    try {
        return decodeStrict(bytes, StandardCharsets.UTF_8);
    } catch (CharacterCodingException e) {
        return new String(bytes, StandardCharsets.ISO_8859_1);
    }
}

测试

System.out.println(decode("señor".getBytes(StandardCharsets.ISO_8859_1))); // prints: señor
System.out.println(decode("señor".getBytes(StandardCharsets.UTF_8))); // prints: señor
System.out.println(decode("€100".getBytes(StandardCharsets.UTF_8))); // prints: €100

【讨论】:

  • +1 这值得一试,我实际上使用的是Charset.forName(strCharset).encode(charBuffer);,默认情况下似乎使用CodingErrorAction.REPLACE,我猜这会导致'�'
  • @Jokkeri 正确,我们想要REPORT,即例外,因此我们可以从 UTF-8 回退到 ISO-8859-1。
猜你喜欢
  • 1970-01-01
  • 2015-10-19
  • 2013-01-20
  • 1970-01-01
  • 1970-01-01
  • 2013-08-27
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多