You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AES/CBC/PKCS5Padding加密内容重启应用后无法解密的技术求助

问题诊断与解决方案

嘿,我一眼就看出问题出在哪了——根本不是密钥比特长度的问题,核心原因是你没有保存加密时使用的IV向量!

问题根源解释

AES-CBC模式的加密和解密必须使用**完全相同的IV(初始化向量)**才能正常工作。你当前的代码里,每次调用generateIv()都会生成一个全新的随机IV,加密后只把密文转成Base64存储,却没有把这个IV保存下来。当应用重启后,你又生成了一个新的IV去解密旧密文,这必然导致解密失败,因为CBC模式的每一块加密都依赖前一块的结果,而IV是第一块的“初始前块”,不匹配的话整个解密流程都会出错。

至于你担心的密钥长度问题:你用PBKDF2生成的是256位AES密钥,只要密码("pass")和盐("salt")不变,每次重启应用生成的密钥都是完全一致的,这部分没有问题。

修复方案

解决思路很简单:把加密时使用的IV和密文一起存储,解密时先还原出这个IV,再用它来初始化解密模式的Cipher。

常见的做法是把IV转成Base64字符串,和密文用一个不会在Base64中出现的分隔符(比如冒号:)拼接起来,或者分开存储到两个字段里。下面是修改后的代码示例:

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Random;

public class AESExample {
    private static final String UTF_8 = StandardCharsets.UTF_8.name();
    // 用于分隔IV和密文的分隔符,Base64中不会出现冒号,所以很安全
    private static final String IV_SEPARATOR = ":";

    private static SecretKey getKeyFromPassword(String password, String salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(StandardCharsets.UTF_8), 65536, 256);
        SecretKey secret = new SecretKeySpec(factory.generateSecret(spec)
                .getEncoded(), "AES");
        return secret;
    }

    private static IvParameterSpec generateIv() {
        byte[] iv = new byte[16];
        new Random().nextBytes(iv);
        return new IvParameterSpec(iv);
    }

    // 加密方法:返回IV:密文的Base64字符串
    public static String encrypt(String plainText, SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
        // 把IV转成Base64,和密文Base64拼接
        String ivBase64 = Base64.getEncoder().encodeToString(iv.getIV());
        String cipherTextBase64 = Base64.getEncoder().encodeToString(cipherText);
        return ivBase64 + IV_SEPARATOR + cipherTextBase64;
    }

    // 解密方法:从IV:密文中还原出明文
    public static String decrypt(String encryptedText, SecretKey key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // 拆分IV和密文
        String[] parts = encryptedText.split(IV_SEPARATOR);
        String ivBase64 = parts[0];
        String cipherTextBase64 = parts[1];

        // 还原IV和密文字节数组
        byte[] iv = Base64.getDecoder().decode(ivBase64);
        byte[] cipherText = Base64.getDecoder().decode(cipherTextBase64);

        // 初始化解密模式的Cipher
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        byte[] plainText = cipher.doFinal(cipherText);
        return new String(plainText, StandardCharsets.UTF_8);
    }

    public static void main(String[] args) throws Exception {
        // 测试文本
        String teste = "teste teste teste teste";
        // 生成密钥(密码和盐固定,重启后生成的密钥一致)
        SecretKey secretKey = getKeyFromPassword("pass", "salt");
        // 生成IV
        IvParameterSpec ivParameterSpec = generateIv();

        // 加密并存储
        String encrypted = encrypt(teste, secretKey, ivParameterSpec);
        System.out.println("加密后存储的内容:" + encrypted);

        // 模拟应用重启:重新生成密钥(和之前一致),然后解密
        SecretKey restartedKey = getKeyFromPassword("pass", "salt");
        String decrypted = decrypt(encrypted, restartedKey);
        System.out.println("解密后的文本:" + decrypted);
    }
}

额外注意事项

  • IV不需要保密,它可以和密文一起公开存储,唯一要求是每次加密都使用随机的IV(你当前的generateIv()已经做到了这一点,这很重要,能保证相同明文加密后得到不同的密文,提升安全性)。
  • 确保分隔符不会出现在Base64字符串中,Base64的字符集是A-Z、a-z、0-9、+、/,所以用冒号:、分号;等都是安全的。

内容的提问来源于stack exchange,提问作者Myphre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 20:57:42