Skip to content

Java AES加密和JS 解密遇到的问题

752字约3分钟

AES加密javascript问题汇总

2024-11-02

问题描述

后端使用的是Java hutool的AES加密,前端使用的是crypto-js的AES解密,前端解密后出现错误提示。

Error: Malformed UTF-8 data
    at Object.stringify (d:\Program\xmyzt\ChemicalSafety-pc\node_modules\crypto-js\core.js:513:24)
    at WordArray.init.toString (d:\Program\xmyzt\ChemicalSafety-pc\node_modules\crypto-js\core.js:268:38)
    at crypto.decryptAES (d:\Program\xmyzt\ChemicalSafety-pc\src\util\crypto.js:55:19)
    at Object.<anonymous> (d:\Program\xmyzt\ChemicalSafety-pc\src\util\crypto.js:88:20)
    at Module._compile (node:internal/modules/cjs/loader:1233:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1287:10)
    at Module.load (node:internal/modules/cjs/loader:1091:32)
    at Module._load (node:internal/modules/cjs/loader:938:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12)
    at node:internal/main/run_main_module:23:47

问题原因

后端将内容加密成了16进制,前端解密时需要将16进制的内容转换成字符串, 且密码用了base64解密了。

Java代码

package org.springblade.modules.system.utils;

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
public class AesUtil {

    /**
     * 密钥
     */
    private static final String BASE64_SECRET = "sGfsTYs6WVzmpRQJBNJuKw==";

    /**
     * aes用来加密解密的byte[]
     */
    private final static byte[] SECRET_BYTES = Base64.decode(BASE64_SECRET);

    /**
     * 根据这个秘钥得到一个aes对象
     */
    private final static AES aes = SecureUtil.aes(SECRET_BYTES);

    /**
     * 加密
     * @param str
     * @return
     */
    public static String encrypt(String str) {
        String encrypt = aes.encryptHex(str);
        return encrypt;
    }

    /**
     * 解密
     * @param str
     * @return
     */
    public static String decrypt(String str) {
        String decrypt = aes.decryptStr(str);
        return decrypt;
    }

    public static void main(String[] args) {
        String djm = "19174085971";
        String encryptData = encrypt(djm);
        System.out.println("加密后:" + encryptData);
        String decryptData = decrypt(encryptData);
        System.out.println("解密后:" + decryptData);
    }
}

JS代码

// aes 解密方法
function decryptAES(data, AES_KEY) {
  // base64解密
  const key = CryptoJS.enc.Base64.parse(AES_KEY);

  // 16进制 > WordArray对象
  const secretText = CryptoJS.enc.Hex.parse(data);

  // AES解密
  const result = CryptoJS.AES.decrypt({ ciphertext: secretText }, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
  });

  return result.toString(CryptoJS.enc.Utf8);
}

注意

加密数据传入CryptoJS.AES.decrypt时,需使用 CryptoJS.AES.decrypt({ ciphertext: secretText }, key, {... }) 写法。

const result = CryptoJS.AES.decrypt({ ciphertext: secretText }, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
});

如果使用 CryptoJS.AES.decrypt(secretText, key, {... }) 写法,则会没有数据,巨坑这个问题,导致排查了很久。

const result = CryptoJS.AES.decrypt(secretText, key, {
    mode: CryptoJS.mode.ECB,
    padding: CryptoJS.pad.Pkcs7
});

两者的区别:

  1. CryptoJS.AES.decrypt({ ciphertext: secretText }, key, {... }):
  • 这种写法中,第一个参数是一个对象,其中包含了 ciphertext 属性,这个属性的值是需要解密的密文数据(secretText)。
  • 这种写法明确指定了密文数据,并且可以在对象中添加其他属性,比如 iv(初始化向量)等。
  • 这种写法通常用于需要更精细控制解密过程的场景。
  1. CryptoJS.AES.decrypt(secretText, key, {... }):
  • 这种写法中,第一个参数直接是需要解密的密文数据(secretText)。
  • 这种写法没有明确指定密文数据的属性,而是直接将密文数据作为第一个参数传递。
  • 这种写法通常用于简单的解密场景,不需要额外的控制参数。

在你的代码中,如果你需要更精细地控制解密过程,比如指定初始化向量或者其他参数,你应该使用第一种写法。如果你只需要简单地解密数据,第二种写法就足够了。

文档