我遇到了从MySQL数据库获取中文字符串的问题。此数据库具有默认设置:
对于我正在使用的架构:
我使用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个字符长。 那么为什么中国汉字不同呢?
由于
答案 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 - 当然是最好的。)
注意:
答案 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;
}