针对包含中文字符的文本的AES加密问题

时间:2017-03-11 17:56:59

标签: java mysql encryption unicode

我遇到了从MySQL数据库获取中文字符串的问题。此数据库具有默认设置:

  • character_set_database:latin1
  • information_schema:utf8
  • 整理:utf8_general_ci

对于我正在使用的架构:

  • charset:latin1
  • 整理:latin1_swedish_ci

我使用SQL转储导入了这个数据库。

这些表包含拉丁数据和中文数据。这是一个全球数据库。

我可以用Java阅读所有这些内容。

当我想加密数据时出现了我的问题。我使用AES和Java加密,并使用Base64.encode

返回字符串中的字节

加密运行正常。我的问题是,当我加密中文字符时,我回来的加密字符串太大了(比如300个字符),虽然中文文本只有几个字符长。

加密代码就像这样

Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encrypted = cipher.doFinal(value.getBytes("UTF-8"));

String encoded = Base64.encodeBase64String(encrypted);
return new String(encoded.getBytes("UTF-8"));

你知道为什么加密值太长了吗? 在加密之前我应该​​区别对待中国的价值吗?

附录:

我调试的时候: 如果我加密这个: 桃草夹芥人蕉芥玉芥花荷子衣兰芥花

我得到结果字符串字符串值= ENCR({FDDabCcaDabp6YSLYCzg / 1MuSzt8QPGEEk3ymeAOW5vERBk + oN3bMSUV5bEbocifr216yqUCObrqDjrrhVwGDqzafWVbELpTQ ==} _ AB_DCD _)

当我调用value.length时,我得到115.而115对我的DB来说太长了。

我认为汉字长度超过两个字节? 这是一个正确的假设吗?

你看到我得到长度= 115的原因吗?

由于

=================================== 附录2

代码是:

    try {
        String english = "Rastapopoulos";
        String chinese = "桃草夹芥人蕉芥玉芥花荷子衣兰芥花";
        String transformationKey = "asdewqayxswedcvf";
        Key aesKey = new SecretKeySpec(transformationKey.getBytes("UTF-8"), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);

        byte[] encrypted1 = cipher.doFinal(english.getBytes("UTF-8"));
        String encoded1 = Base64.encodeBase64String(encrypted1);

        byte[] encrypted2 = cipher.doFinal(chinese.getBytes("UTF-8"));
        String encoded2 = Base64.encodeBase64String(encrypted2);

        System.out.println("Original length: " + english.length() + "\tEncrypted length: " + encoded1.length() + "\t" + encoded1);
        System.out.println("Original length: " + chinese.length() + "\tEncrypted length: " + encoded2.length() + "\t" + encoded2);
    } catch (Exception e) {
        e.printStackTrace();
    }

并给我以下输出

原始长度:13加密长度:24 V4y9u3tNQaH81BAcqi1XZg == 原始长度:16加密长度:88 KTMAxhqALAlXfjaOLsBlbj7jbqz + 8M4F0AlvvUU5OmrvT + D7MGQHseYKm32V46bqyNbHtu91JC4sQ + mVoWp / wQ ==

与你得到的相似

我的问题是我无法将其写回数据库,因为它大于字段的最大长度。 但我不明白的是为什么我的13-15个字符的英文字符串总是给我24个字节的长度,以及为什么我的16个字节的汉字给我一个88字节长的加密值。

这种差异来自哪里?

DB中的值非常小,少于20个字符,因此加密时我不应该有任何问题。结果总是少于24个字符长。 那么为什么中国汉字不同呢?

由于

3 个答案:

答案 0 :(得分:2)

在MySQL中,在任何包含中文的列上使用CHARACTER SET utf8mb4(不是latin1,而不是utf8)。这对应于MySQL之外的UTF-8

除非恰好是某些源文本的编码,否则请勿使用UTF16。

SELECT长度(aes_encrypt("桃草夹芥人蕉芥玉芥花荷子衣兰芥花",' AES')) - > 64;我不知道你在哪里得到24.此外,aes_encrypt的输出总是16字节的倍数。

如果要将加密值存储在MySQL中,则必须执行以下操作之一:

  • 声明列VARBINARY(...)BLOB
  • 使用VARCHAR / TEXT列,但请输入aes_encrypt输出的HEX / BASE64

答案 1 :(得分:1)

UTF-8不是中文字符的最佳编码,因为它们主要编码为多个字节。

此外,CBC模式+ PKCS#7填充(在Java中称为PKCS5Padding)不是最有效的模式,因为它需要大的随机IV和填充。

因此要使用较小的编码值,请尝试使用UTF-16和CTR编码,其中IV仅包含8字节的随机数(包含在密文中)且没有填充。

示例代码:

SecureRandom rng = new SecureRandom();
SecretKey aesKey = new SecretKeySpec(new byte[16], "AES");

String chinese = "桃草夹芥人蕉芥玉芥花荷子衣兰芥花";
byte[] utf8Chinese = chinese.getBytes(UTF_8);
System.out.printf("UTF-8    encoded : %d bytes: %s%n", utf8Chinese.length, Hex.toHexString(utf8Chinese));

{
    Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");

    byte[] ivBytes = new byte[aesCBC.getBlockSize()];
    rng.nextBytes(ivBytes);
    aesCBC.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(ivBytes));

    byte[] cipherTextCBC = aesCBC.doFinal(utf8Chinese);
    byte[] ivAndCipherTextCBC = Arrays.concatenate(ivBytes, cipherTextCBC);

    System.out.printf("UTF-8, CBC    encoded : %d bytes: %s%n", ivAndCipherTextCBC.length, Hex.toHexString(ivAndCipherTextCBC));
}

byte[] utf16Chinese = chinese.getBytes(UTF_16BE);
System.out.printf("UTF-16BE encoded : %d bytes: %s%n", utf16Chinese.length, Hex.toHexString(utf16Chinese));

{
    Cipher aesCTR = Cipher.getInstance("AES/CTR/NoPadding");

    byte[] nonce = new byte[8];
    rng.nextBytes(nonce);
    byte[] initialCounterValue = new byte[8];
    byte[] ivForCTR = Arrays.concatenate(nonce, initialCounterValue);
    aesCTR.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(ivForCTR));

    byte[] cipherTextCTR = aesCTR.doFinal(utf16Chinese);
    byte[] ivAndCipherTextCTR = Arrays.concatenate(ivForCTR, cipherTextCTR);

    System.out.printf("UTF-16BE, CTR encoded : %d bytes: %s%n", ivAndCipherTextCTR.length, Hex.toHexString(ivAndCipherTextCTR));
}

最后输出:

UTF-8    encoded : 48 bytes: e6a183e88d89e5a4b9e88aa5e4babae89589e88aa5e78e89e88aa5e88ab1e88db7e5ad90e8a1a3e585b0e88aa5e88ab1
UTF-8, CBC    encoded : 80 bytes: c109837322fcd5472539bb7cb51dd6841cea744273979cdbed54d9db019747d41b4e784c22f8e6384e92135ff37747797796baa438f26c914dc5ab99b17afc30771e0b18263d2061d971ef54c457c1b9
UTF-16BE encoded : 32 bytes: 68438349593982a54eba854982a5738982a582b183775b508863517082a582b1
UTF-16BE, CTR encoded : 48 bytes: 9c6afe2d8899284f0000000000000000cad3877bee435324ffa671f956781f2838279fe56e811c9ba5bcf98a6cc98a7f

你有它:32个字节。那就是 base <64>之前的,这会将密文扩展到另一个1/3,至少当结果被放入使用ASCII兼容编码(如UTF-8)的列时。请注意,您不希望在加密后使用UTF-16作为基本64编码的结果(仅存储二进制文件 - 无需编码到基数64 - 当然是最好的。)

注意:

  • 需要CBC的IV和CTR模式的nonce;如果他们没有被使用,那么加密不提供完全的机密性(并且对于CTR大约没有机密性);
  • 使用上述方案,不使用相同的密钥加密超过2 ^ 16个明文。

答案 2 :(得分:0)

这个方法对我有用

 public String encode3Des(String key, String requestString) {
        try {
            byte[] seed_key = (new String(key)).getBytes();
            String plaintxt = "";
            try {
            String ISO = new String(requestString.getBytes("gb2312"), "ISO8859-1");
            plaintxt = new String(ISO.getBytes("ISO8859-1"), "gb2312");
            } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            }
            SecretKeySpec keySpec = new SecretKeySpec(seed_key, "TripleDES");
            Cipher nCipher = Cipher.getInstance("TripleDES");
            nCipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byte[] cipherbyte = nCipher.doFinal(plaintxt.getBytes("UTF-8"));
            String encodeString = DatatypeConverter.printBase64Binary(cipherbyte);
            return encodeString;
        } catch (InvalidKeyException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BadPaddingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    
        }