升级到JDK17,AES解密失败:Given final block not properly padded. Such issues can arise if a bad key is used d

描述

项目从JDK8升级到了JDK17,在调试代码的过程中发现有AES解密失败的错误日志,如下:
javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
	at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:859)
	at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:939)
	at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:735)
	at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)`
	at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2205)
	at com.papercollection.winterview.wis.AESUtil.operator(AESUtil.java:54)
	at com.papercollection.winterview.wis.AESUtil.decrypt(AESUtil.java:77)
	at com.papercollection.winterview.wis.AESUtil.main(AESUtil.java:100)

AES加解密代码

import org.springframework.util.Base64Utils;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class AESUtil {
    /**
     * AES加密字符串
     *
     * @param content  需要被加密的字符串
     * @param password 加密需要的密码
     * @return 密文
     */
    public static byte[] encrypt(String content, String password) {
        try {
            int mode = Cipher.ENCRYPT_MODE;
            byte[] byteContent = content.getBytes("utf-8");
            return operator(password, mode, byteContent);
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static byte[] operator(String password, int mode, byte[] byteContent) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        SecretKey secretKey = getSecretKey(password);

        Cipher cipher = Cipher.getInstance("AES");// 创建密码器
        cipher.init(mode, secretKey);// 初始化为加密模式的密码器
        byte[] result = cipher.doFinal(byteContent);// 加密
        return result;
    }

    private static SecretKey getSecretKey(String password) throws NoSuchAlgorithmException {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创建AES的Key生产者
        kgen.init(128, new SecureRandom(password.getBytes()));// 利用用户密码作为随机数初始化出
        //加密没关系,SecureRandom是生成安全随机数序列,password.getBytes()是种子,只要种子相同,序列就一样,所以解密只要有password就行
        SecretKey secretKey = kgen.generateKey();// 根据用户密码,生成一个密钥
        printSecretKey(secretKey);
        return secretKey;
    }

    /**
     * 解密AES加密过的字符串
     *
     * @param content  AES加密过过的内容
     * @param password 加密时的密码
     * @return 明文
     */
    public static byte[] decrypt(byte[] content, String password) {
        try {
            int mode = Cipher.DECRYPT_MODE;
            return operator(password, mode, content);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        String content = "1993";
        String password = "加密密码";
        System.out.println("需要加密的内容:" + content);
        byte[] encrypt = encrypt(content, password);
        String base64Encode = Base64Utils.encodeToString(encrypt);

        byte[] byte2 = Base64Utils.decodeFromString(base64Encode);
        byte[] decrypt = decrypt(byte2, password);
        System.out.println("解密后的内容:" + new String(decrypt, "utf-8"));
    }


    public static void printSecretKey(SecretKey secretKey) {
        if (secretKey instanceof SecretKeySpec) {
            SecretKeySpec spec = (SecretKeySpec) secretKey;
            System.out.println("Algorithm: " + spec.getAlgorithm());
            System.out.println("key[]: " + Arrays.toString(spec.getEncoded()));
        }
    }
}

分析

项目中的代码也是参考的网上样例,代码中有个很奇怪的地方是,每次都要重新生成一次secretKey,尝试打印该变量

JDK8环境运行

在这里插入图片描述

JDK17环境运行

在这里插入图片描述

可以看到JDK17两次生成的secretKey值不同

手动调试

打断点调试在解密时手动修改SecretKey的值为加密时的值,发现时可以解密成功的
在这里插入图片描述
运行结果

在这里插入图片描述

小结

那么问题的直接原因应该是JDK8时每次生成的secretKey都相同,而JDK17都不同,导致加密后解密失败

新问题

那么就引申出两个新问题

  • AES是对称加密,为什么每次都要使用KeyGenerator生成SecretKey,双方约定使用同样的key不可以吗
  • 为什么在JDK8,每次生成的SecretKey相同,而到了JDK17会变的不同了
1. 为什么每次都要使用KeyGenerator生成SecretKey

我搜索了网上的一些样例和代码,每次生都成新的secretKey是不正确的做法,正常的是双方约定好一个16位或者其他符合要求位数的密钥,进行加解密操作

2. 为什么到了JDK17,每次生成的SecretKey会不同
    private static SecretKey getSecretKey(String password) throws NoSuchAlgorithmException {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创建AES的Key生产者
        kgen.init(128, new SecureRandom(password.getBytes()));// 利用用户密码作为随机数初始化出
        //加密没关系,SecureRandom是生成安全随机数序列,password.getBytes()是种子,只要种子相同,序列就一样,所以解密只要有password就行
        SecretKey secretKey = kgen.generateKey();// 根据用户密码,生成一个密钥
        printSecretKey(secretKey);
        return secretKey;
    }

生成secretKey使用的KeyGenerator是用SecureRandom初始化的,在JDK8中中,使用的随机算法是SHA1PRNG,而到了JDK17中使用的算法是DRBG.
这里需要提一下,JDK17与JDK8相比,SecureRandom的构造方法发生了变化,随机算法的选取规则也不一样了

  • JDK8
    在这里插入图片描述

  • JDK17
    在这里插入图片描述

SHA1PRNG算法

验证代码,指定使用SHA1PRNG作为随机算法

    private static SecretKey getSecretKey(String password) throws NoSuchAlgorithmException {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创建AES的Key生产者
        //kgen.init(128, new SecureRandom(password.getBytes()));// 利用用户密码作为随机数初始化出
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        secureRandom.setSeed(password.getBytes());
        kgen.init(128, secureRandom);

        SecretKey secretKey = kgen.generateKey();// 根据用户密码,生成一个密钥
        printSecretKey(secretKey);
        return secretKey;
    }
  • windows
环境信息:windows11
JDK版本17

在这里插入图片描述

  • linux
环境信息:centos
JDK版本1.8

在这里插入图片描述

结论

SHA1PRNG是跨平台和版本的,生成的SecretKey只和指定的因子有关

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
"Given final block not properly padded. Such issues can arise if a bad key is used during decryption"报错是因为解密时使用了错误的密钥。在AES解密过程中,需要使用与加密过程中相同的密钥来正确解密数据。 根据引用中的代码,每次生成的SecretKey可以不同。这是因为在JDK17中,KeyGenerator生成SecretKey的方式不同于之前的版本。JDK17中的KeyGenerator使用了更安全的随机数生成方法,每次生成的密钥都会不同。而在JDK8中,使用的是基于密码种子生成随机数序列,只要种子相同,生成的密钥就相同。 为了确保数据的安全性,每次使用KeyGenerator生成SecretKey是一个好的做法。这样可以避免密钥泄露或被猜测的风险。双方约定使用同样的密钥也是可以的,但需要保证密钥的安全性和保密性。如果密钥被泄露或被破解,加密的数据将会暴露在风险中。因此,使用KeyGenerator生成密钥可以增加数据的安全性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [“Given final block not properly padded. Such issues can arise if a bad key is used during“错误解决](https://blog.csdn.net/u012660464/article/details/128341544)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [升级JDK17AES解密失败Given final block not ... Such issues can arise if a bad key is used d](https://blog.csdn.net/libai245/article/details/130300658)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值