java AES加密 前端CryptoJS AES解密

问题

java加密生成base64位字符串,前端使用Java提供的密钥,前端无法解密出数据。
java后台加密使用AES/ECB/PKCS5Padding

数据准备:
一个固定秘钥,互相约定

B7E1FF62FC0B4392B81432D44CE62F88

一个待加密字符串 ,待测的业务数据

{“SYSCODE”:“AES5500”,“SYSTIME”:“20221201105835”}

原始代码

java

public static String encrypt(String content, String key) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            byte[] byteContent = content.getBytes("utf-8");
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
            byte[] result = cipher.doFinal(byteContent);
            return byte2Base64(result);
        } catch (Exception var5) {
            return null;
        }
    }

    public static String decrypt(String content, String key) {
        try {
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
            byte[] result = cipher.doFinal(base642Byte(content));
            return new String(result, "utf-8");
        } catch (Exception var4) {
            return null;
        }
    }

    /**
     * 生成加密秘钥
     * @param key
     * @return
     */
    private static SecretKeySpec getSecretKey(String key) {
        try {
            KeyGenerator kg = KeyGenerator.getInstance("AES");
            // 指定算法名称,不同的系统上生成的key是相同的。
            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
            random.setSeed(key.getBytes());
            //AES 要求密钥长度为 128
            kg.init(128, random);
            //生成一个密钥
            SecretKey secretKey = kg.generateKey();
            // 转换为AES专用密钥
            return new SecretKeySpec(secretKey.getEncoded(), "AES");
        } catch (NoSuchAlgorithmException var4) {
            return null;
        }
    }
   
    public static String byte2Base64(byte[] bytes) {
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(bytes);
    }

    public static byte[] base642Byte(String base64Key) throws IOException {
        BASE64Decoder decoder = new BASE64Decoder();
        return decoder.decodeBuffer(base64Key);
    }

前端代码

let obj = JSON.stringify({"SYSCODE":"AES5500","SYSTIME":"20221201105835"})
const key= "B7E1FF62FC0B4392B81432D44CE62F88"
/**
 * 加密 --api
 * @param word string
 * @returns {*} string
 */
 function encrypt(word){//word 为 string  mode为加密规则
    var secretKey = CryptoJS.enc.Utf8.parse(key);//key  为你的秘钥
    var srcs = CryptoJS.enc.Utf8.parse(word);
    var encrypted = CryptoJS.AES.encrypt(srcs, secretKey, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
    return encrypted.toString();
}
/**
 * 解密 --api
 * @param word string
 * @returns {*} string
 */
function decrypt(word){
    var secretKey = CryptoJS.enc.Utf8.parse(key);
    var decrypt = CryptoJS.AES.decrypt(word, secretKey, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
    return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}

网上基本上大同小异,如果按照上述代码,无法解密成功。

原因分析

在Java中,AES的实际密钥需要用到KeyGenerator 和 SecureRandom。而这个秘钥生成器只有java才有,其他语言没有,当然前端使用的CryptoJS也不知道这个是啥玩意。

问题出在getSecretKey 这个方法,使用了java特有的KeyGenerator ,对秘钥进行了指定算法SHA1PRNG再次生成一个新秘钥。导致前端使用的秘钥它不是真正的秘钥。

解决方法

1、去除KeyGenerator

第一种解决思路,既然是java特有的秘钥生成器,那我不用就好了,不要再次生成。
直接用约定的秘钥就好了。后台java把代码删减一下,前端代码不需要修改。
大部分人也是这样用的,网上有一堆声明了KeyGenerator 但是实际上却未使用的示例代码。

  private static SecretKeySpec getSecretKey(String key) {
        //            KeyGenerator kg = KeyGenerator.getInstance("AES");
//            // 指定算法名称,不同的系统上生成的key是相同的。
//            SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
//            random.setSeed(key.getBytes());
//            //AES 要求密钥长度为 128
//            kg.init(128, random);
//            //生成一个密钥
//            SecretKey secretKey = kg.generateKey();
        // 转换为AES专用密钥
        return new SecretKeySpec(key.getBytes(), "AES");
    }
2、解读KeyGenerator的执行结果

大部分都是第一种解决思路,但是有时候在某种情况下总是因为某些原因无法去修改这个加解密方式,只能去适应他。
KeyGenerator生成的秘钥最终也是转换成了byte数组。那就直接将最终转换的byte数组打印生成Hex字符串出来,看看长什么样子。

        SecretKey secretKey = kg.generateKey();
        //将最终的秘钥以16进制字符串打印到控制台
        toHexString(secretKey.getEncoded());
        
        打印结果:fb39ab6cea8300f6a9e115a46d3bb78a

前端改造
知道java对秘钥进行了什么操作之后,前端复刻java的操作就行。

  • 前端手上是一个原始秘钥,需要使用SHA1PRNG指定算法进行解析
let hexStr=CryptoJS.SHA1(CryptoJS.SHA1('B7E1FF62FC0B4392B81432D44CE62F88')).toString().substring(0, 32);

此时得到了最终的hex字符串,跟java中秘钥byte数组转成Hex字符串一致。
此时还不能直接用 CryptoJS.AES.decrypt 进行解密,直接解密会失败。

  • hex字符串转成Hex
    还需要进行特定的数据类型转换,此时还是字符串,解密时需要真实的HEX

secretKey = CryptoJS.enc.Hex.parse(hexStr)
此时的secretKey 才是最终的秘钥,CryptoJS才能正确识别

/**
 * 解密 --api
 * @param word string
 * @returns {*} string
 */
function decrypt(word){
    var secretKey = CryptoJS.enc.Utf8.parse(key);
    var hexStr=CryptoJS.SHA1(CryptoJS.SHA1(key)).toString().substring(0, 32);
    secretKey = CryptoJS.enc.Hex.parse(hexStr);
    var decrypt = CryptoJS.AES.decrypt(word, secretKey, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
    return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}

完整的前端代码

let base = 'B7E1FF62FC0B4392B81432D44CE62F88';
function decrypt(word){ //word 为base64字符串
    var secretKey = CryptoJS.enc.Utf8.parse(base);
   //==================java使用了KeyGenerator SHA1PRNG 算法 就加这2行代码,没加就去掉=======
    var hexStr=CryptoJS.SHA1(CryptoJS.SHA1(base)).toString().substring(0, 32);
    secretKey = CryptoJS.enc.Hex.parse(hexStr);
	//======================================================================
    var decrypt = CryptoJS.AES.decrypt(word, secretKey, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
    return CryptoJS.enc.Utf8.stringify(decrypt).toString();
}	 
function encrypt(word){//word 为 string  
    var secretKey = CryptoJS.enc.Utf8.parse(base);//key  为你的秘钥
	 //==================java使用了KeyGenerator SHA1PRNG 算法 就加这2行代码,没加就去掉=======
    var hexStr=CryptoJS.SHA1(CryptoJS.SHA1(base)).toString().substring(0, 32);
    secretKey = CryptoJS.enc.Hex.parse(hexStr);
	//======================================================================
    var srcs = CryptoJS.enc.Utf8.parse(word);
    var encrypted = CryptoJS.AES.encrypt(srcs, secretKey, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
    return encrypted.toString();
}

简单页面测试打印一下,解密正常。拿前端的加密串在java后台解密也正常
在这里插入图片描述

拓展

其他的语言,像C#之类的也是按照类似的思路进行加解密即可。可以使用HEX数组进行加解密

遇到的问题

1、CryptoJS.SHA1进行2次,为什么进行2次?
后续研究…
2、java加密给出的密文是base64加密之后的,密文中携带换行字符,导致解密失败。

Base64编码包有很多,使用不同标准的Base64编码包,会导致密文有所区别。
根据RFC822规定,BASE64Encoder编码每76个字符,还需要加上一个回车换行
部分Base64编码的java库还按照这个标准实行
sun.mis包:encode有换行符
Java 8的java.util包:encode无换行符
Apache Commons Codec:encode无换行符

后台使用的sun.mis包里面base64,导致密文存在换行。
从java后台获取到密文数据时,要进行\r\n的字符处理,将其换行符替换为空拼接在一起。直接解密会读取到\r\n导致解密失败。

建议使用Java 8的java.util包

	//编码
    String encodeToString = Base64.getEncoder().encodeToString(message.getBytes());
    //解码
    byte[] decode = Base64.getDecoder().decode(encodeToString); 
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
JavaAES加密解密是一种常用的对称加密算法,它可以将明文加密成密文,并且可以使用相同的密钥进行解密。在Java中,常用的JavaAES加密解密实现方式有两种:一种是基于Java自带的Cipher类实现的,另一种是基于第三方库实现的。 如果使用Java自带的Cipher类,可以按照以下步骤实现JavaAES加密解密: 1. 生成密钥,可以使用KeyGenerator类生成密钥。 2. 创建Cipher对象,设置加密模式和填充方式。 3. 初始化Cipher对象,设置加密或解密模式,以及密钥。 4. 调用Cipher的doFinal方法进行加密或解密操作。 以下是JavaAES加密解密的代码示例: ```java import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; public class JavaAESTest { public static void main(String[] args) throws Exception { String plaintext = "Hello, World!"; //明文 String password = "123456"; //密钥 //生成密钥 SecretKey secretKey = getSecretKey(password); //加密 byte[] ciphertext = encrypt(plaintext.getBytes(StandardCharsets.UTF_8), secretKey); System.out.println("加密后:" + new String(ciphertext, StandardCharsets.UTF_8)); //解密 byte[] decrypted = decrypt(ciphertext, secretKey); System.out.println("解密后:" + new String(decrypted, StandardCharsets.UTF_8)); } private static SecretKey getSecretKey(String password) throws NoSuchAlgorithmException { //生成AES算法的KeyGenerator对象 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(128); //指定密钥长度为128位 //生成一个128位的随机安全密钥 return keyGenerator.generateKey(); } private static byte[] encrypt(byte[] plaintext, SecretKey secretKey) throws Exception { //创建Cipher对象,设置加密模式和填充方式 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); //初始化Cipher对象 //加密并返回结果 return cipher.doFinal(plaintext); } private static byte[] decrypt(byte[] ciphertext, SecretKey secretKey) throws Exception { //创建Cipher对象,设置解密模式和填充方式 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey); //初始化Cipher对象 //解密并返回结果 return cipher.doFinal(ciphertext); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值