如何检测编码不匹配

问题描述 投票:0回答:3

我有一堆旧的 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 ...>

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

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

if (new String(utf8decryptedCharArray).contains("�")) {
    // Revert to doing the decrypting with ISO-8859-1
    decryptAsISO...
}
java encryption encoding
3个回答
4
投票

解密时,你会得到原始的字节序列(步骤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。尽管如此,错误的概率仍不为零。

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


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

0
投票

我不得不面对类似的问题。我正在使用的应用程序应该接受基本拉丁字符和拉丁扩展 A 字符。 主要问题是接受重音字符 (éèêàa...)。

我尝试使用正则表达式、编码器、解码器......最后我得到了这个对我来说工作正常的结果。

  • 首先使用 Apache 中的 StringUtils 删除所有重音字符

  • 然后检查每个字符字母表检查unicodblock

    私有静态最终列表 SUPPORTED_ALPHABETS = List.of( 字符.UnicodeBlock.BASIC_LATIN, 字符.UnicodeBlock.LATIN_EXTENDED_A);

      public boolean hasWrongEncodedChar(final String text) {
          return StringUtils.isNotEmpty(text) && 
              StringUtils.stripAccents(text).chars().anyMatch(value ->
                 !SUPPORTED_ALPHABETS.contains(Character.UnicodeBlock.of(value)));
      }
    

希望有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.