AES加密的中文乱码与Java默认编码

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
  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在PHP和Java之间进行AES加密解密时出现乱码问题,可能是因为两个语言之间使用了不同的编码方式。为了解决这个问题,我们需要在两个语言之间选择一种通用的编码方式,以确保加密和解密时的数据一致。 一种通用的编码方式是Base64编码。在PHP中,可以使用base64_encode()函数将加密后的数据转换为Base64编码。在Java中,可以使用java.util.Base64类进行编码和解码。在进行解密操作之前,需要先将Base64编码的数据解码成原始二进制数据。 下面是一个PHP和Java之间进行AES加密解密的示例代码: PHP代码: ``` $key = 'your_key'; $data = 'your_data'; // 加密 $encrypted = openssl_encrypt($data, 'AES-128-ECB', $key, OPENSSL_RAW_DATA); $base64_encrypted = base64_encode($encrypted); // 将加密后的数据传递给Java ``` Java代码: ``` import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class AESUtils { private static final String ALGO = "AES"; private static final String MODE = "ECB"; private static final String PADDING = "PKCS5Padding"; public static byte[] decrypt(byte[] key, byte[] encryptedData) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec(key, ALGO); Cipher cipher = Cipher.getInstance(ALGO + "/" + MODE + "/" + PADDING); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); return cipher.doFinal(encryptedData); } public static void main(String[] args) throws Exception { String keyStr = "your_key"; String encryptedStr = "your_base64_encrypted_data"; // 解码Base64编码的数据 byte[] encrypted = Base64.getDecoder().decode(encryptedStr); byte[] key = keyStr.getBytes(); // 解密 byte[] decrypted = AESUtils.decrypt(key, encrypted); String data = new String(decrypted); System.out.println(data); } } ``` 在上面的示例中,PHP代码将数据加密并转换为Base64编码,然后将其传递给Java代码。Java代码解码Base64编码的数据,使用AES算法进行解密,并将解密后的数据转换为字符串输出。注意,在实际应用中,需要确保加密和解密使用相同的密钥、算法、模式和填充方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值