AES加密的中文乱码与Java默认编码
win11环境下 ++ idea开发的项目接口有加密需求,暂时使用AES完成如下本地开发/测试都能正常解密,在自己的Linux(centos)机器测试接口也没有出现中文乱码的问题.UAT的时候用户请求接口得到密文后解密后中文就乱码为???,确认不开加密明文传输中文不会乱码,问题出在AES加密上。
0. 背景
win11环境下 + java8 + idea
开发的项目接口有加密需求,暂时使用AES完成,javaAES工具类代码如下
public static String aesEncrypt(String content, String key) throws Exception {
//指定加密算法
Cipher cipher = Cipher.getInstance("AES");
//创建加密规则:指定key和加密类型
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
//指定加密模式为加密,指定加密规则
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
//调用加密方法
byte[] result = cipher.doFinal(content.getBytes());
//用Base64编码
return new String(Base64.getEncoder().encode(result));
}
public static String aesDecrypt(String content, String key) throws Exception {
//Base64解码
byte[] result = Base64.getDecoder().decode(content);
//指定加密算法
Cipher cipher = Cipher.getInstance("AES");
//创建加密规则:指定key和加密类型
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
//指定加密模式为解密,指定加密规则
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
return new String(cipher.doFinal(result));
}
本地开发/测试都能正常解密,在自己的Linux(centos)机器测试接口也没有出现中文乱码的问题.
之后公司暂时只有Windows服务器空闲,只提供了windows服务器用于部署接口程序进行UAT,
UAT的时候用户请求接口得到密文后解密后中文就乱码为 ???
,确认不开加密明文传输中文不会乱码,问题出在AES加密上
1.原因与正确写法
Java改为下面的代码后中文就正常了
public String encrypt(String plainText, String key) throws Exception {
Cipher cipher = Cipher.getInstance(AES);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] result = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(result);
}
public String decrypt(String encryptedText, String key) throws Exception {
Cipher cipher = Cipher.getInstance(AES);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), AES);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
与之前代码相比,显式声明了getBytes()方法应该采用utf-8编码
原因:
jdk8下,getBytes()方法来自String类,最终调用了StringCoding类的encode方法.方法的默认编码首先是取平台默认编码, Charset.defaultCharset()
!
String csn = Charset.defaultCharset().name();
try {
// use charset name encode() variant which provides caching.
return encode(csn, ca, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
在windows服务器上使用 Charset.defaultCharset().name();
发现编码集是 windows-1252
!非utf-8,自然就有问题!!
所以代码中需要显示声明编码为utf-8!!!
Windows-1252 编码是一种单字节编码,它主要用于表示西欧语言中的字符,包括英语、法语、德语等。在 Windows-1252 编码中,并没有包含中文字符所需的字节范围,因此无法正确表示中文字符。
2.Java默认编码
上述问题解决后,我才意识了自己有一个误解:java的默认编码是utf-8
现在想想,这样的误解来自学习时老师强调创建新项目后,总要设置/检查是否为utf-8编码,用的时间长了,就默认java编码就是utf-8,真是不应该
查询发现,
-
JDK18及之后,java默认编码根据
jep400
变成了UTF-8 -
在JDK18之前(例如jdk8),默认字符集在很大程度上取决于操作系统:大部分Linux上是UTF-8;Windows机器上可能是Windows-1252(尤其是在西欧)或Windows-31j(日语)
虽然这一次问题的大头是 没注意Windows服务器的原因,不过代码中没有强调utf-8也的确是一个漏洞,以后在做字节序列和字符序列(byte/char/String) 需要特别注意编码问题!
3.参考阅读
附上python请求api代码的加解密工具类方法
python3.12.X
import base64
import json
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
key="你的密钥"
def encrypt_aes(plain_text, key):
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
padded_text = pad(plain_text.encode('utf-8'), AES.block_size)
encrypted_bytes = cipher.encrypt(padded_text)
return base64.b64encode(encrypted_bytes).decode('utf-8')
def decrypt_aes(encrypted_text, key):
cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
encrypted_bytes = base64.b64decode(encrypted_text)
decrypted_bytes = cipher.decrypt(encrypted_bytes)
unpadded_text = unpad(decrypted_bytes, AES.block_size)
return unpadded_text.decode('utf-8')
如果找不到Crypto模块,
ModuleNotFoundError: No module named ‘Crypto’
大概率是 https://blog.csdn.net/Ghjkku/article/details/138223601 这篇文章里提到的问题,
解决办法:按从上至下顺序依次执行终端命令
pip uninstall crypto
pip uninstall pycryptodome
pip install pycryptodome
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)