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

如何基于密码和盐生成javax.crypto.SecretKey解决DESede解密报错

问题描述
  • 开发需求:基于密码(password)与盐值(salt)生成javax.crypto.SecretKey实例,用于加解密操作
  • 约束条件:现有系统使用DESede/ECB/PKCS5Padding算法完成加解密,数据库已存储大量存量加密数据,不允许修改现有加解密逻辑
  • 故障现象:自行实现PBKDF2派生密钥逻辑后,对接现有解密流程抛出如下异常:
key.getAlgorithm(): DESede
encryptedData: [B@31dc339b
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
    at com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
    at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
    at com.sun.crypto.provider.DESedeCipher.engineDoFinal(DESedeCipher.java:294)
    at javax.crypto.Cipher.doFinal(Cipher.java:2168)
    at com.arjun.mytest.PMAdminKeyTest.main(PMAdminKeyTest.java:41)
  • 故障复现代码:
import java.security.KeyStore;
import java.security.Provider;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class PMAdminKeyTest {

    public static void main(String[] args) throws Exception {
        // Requirement is to generate Key based on password and salt
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec keySpec = new PBEKeySpec("password".toCharArray(), "salt".getBytes(), 65536, 192);
        SecretKey key = new SecretKeySpec(secretKeyFactory.generateSecret(keySpec).getEncoded(), "DESede");

        System.out.println("key.getAlgorithm(): " + key.getAlgorithm());

        byte[] data = "12345678".getBytes("UTF8");

        // Existing encrypt and decrypt code. There is already lot of data in DB
        // encrypted in this manner. I dont think I can change this code.
        Cipher cipher = Cipher.getInstance(key.getAlgorithm() + "/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encryptedData = cipher.doFinal(data);

        System.out.println("encryptedData: " + encryptedData.toString());

        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decryptedData = cipher.doFinal(data);

        System.out.println("decryptedData: " + decryptedData.toString());
    }

}
故障根因

两个问题共同导致异常抛出:

  1. 代码笔误:解密调用cipher.doFinal()时传入的是原始明文data,而非加密生成的encryptedData,属于低级逻辑错误
  2. 密钥不合法:DESede(3DES)算法要求密钥每个字节的最低位为奇偶校验位,每个字节中二进制1的个数必须为奇数。PBKDF2直接派生的192位密钥字节不满足该校验规则时,JCE在初始化Cipher时会自动调整密钥位完成校验,导致实际用于加解密的密钥和生成的原始密钥不一致,就算修正笔误,也会出现密钥不匹配导致的解密失败。
兼容实现方案

按照以下步骤调整即可兼容现有加解密逻辑:

  • 修正解密阶段的参数错误,传入加密后的字节数组进行解密
  • 对PBKDF2派生的原始密钥字节做奇偶校验位调整,生成完全符合DESede规范的密钥,避免JCE自动修改密钥位
  • 固定PBKDF2的迭代次数、盐值、密钥长度参数,保证相同密码输入下每次生成的密钥完全一致

调整后的可运行代码如下:

import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class PMAdminKeyTest {

    public static void main(String[] args) throws Exception {
        // 固定配置参数,生产环境请妥善保管盐值、迭代次数配置,不要随意修改
        String password = "password";
        byte[] salt = "salt".getBytes();
        int iterationCount = 65536;
        int keyLength = 192;

        // PBKDF2派生原始密钥
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
        byte[] rawKey = secretKeyFactory.generateSecret(keySpec).getEncoded();

        // 调整密钥字节,满足DESede奇偶校验要求
        adjustDESedeParityBit(rawKey);
        SecretKey key = new SecretKeySpec(rawKey, "DESede");

        System.out.println("key.getAlgorithm(): " + key.getAlgorithm());
        byte[] data = "12345678".getBytes("UTF8");

        // 现有加解密逻辑无需任何修改
        Cipher cipher = Cipher.getInstance(key.getAlgorithm() + "/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encryptedData = cipher.doFinal(data);
        System.out.println("encryptedData: " + encryptedData);

        // 修正参数错误:解密传入加密后的字节数组
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] decryptedData = cipher.doFinal(encryptedData);
        System.out.println("decryptedData: " + new String(decryptedData, "UTF8"));
    }

    /**
     * 调整3DES密钥的奇偶校验位,保证每个字节二进制1的个数为奇数
     */
    private static void adjustDESedeParityBit(byte[] key) {
        for (int i = 0; i < key.length; i++) {
            int b = key[i] & 0xFE;
            int count = Integer.bitCount(b);
            // 若1的个数为偶数,将最低位置1保证奇数校验
            key[i] = (byte) ((count % 2 == 0) ? (b | 0x01) : b);
        }
    }
}

注意:DESede算法本身安全强度较低,ECB模式也存在固有安全风险,若后续有系统重构机会建议替换为AES-GCM等更安全的加密方案,当前方案仅用于兼容存量业务,不要用于新业务开发。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 06:03:12