使用 SecureRandom 衍生 DES 金鑰在跨平台/多執行緒場景下極易導致 BadPaddingException: Given final block not properly padded。本文剖析根因,提供基於 SecretKeyFactory 的標準修復方案。
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
加密正常,解密拋出。多執行緒並發測試時複現率極高。
// 錯誤寫法:依賴 SecureRandom 衍生金鑰
KeyGenerator kg = KeyGenerator.getInstance("DES");
kg.init(new SecureRandom(strKey.getBytes())); // 種子來源不穩定
Key key = kg.generateKey();
SecureRandom 實現依賴 JCE Provider(SunJCE、BC 等),不同 JDK 版本/廠商種子擴展算法不同。KeyGenerator 實例時狀態競爭加劇問題。// 修復後的金鑰生成
private Key setKey(String strKey) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");
DESKeySpec keySpec = new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
return factory.generateSecret(keySpec); // 確定性生成,跨平台一致
} catch (Exception e) {
throw new RuntimeException("DES 金鑰生成失敗", e);
}
}
優勢:
| 原因 | 檢查方式 |
|---|---|
| 加密/解密金鑰不一致 | 打印金鑰 getEncoded() 十六進制對比 |
| 填充模式不匹配 | 統一指定 Cipher.getInstance("DES/ECB/PKCS5Padding") |
| 資料截斷/損壞 | Base64 編碼/解碼是否完整、傳輸過程是否截斷 |
| 資料長度非 8 位元組倍數(無填充模式) | 必須使用 PKCS5Padding 或手動填充 |
public class DesUtil {
private static final String ALGO = "DES";
private static final String TRANSFORMATION = "DES/ECB/PKCS5Padding";
private final Cipher encryptCipher;
private final Cipher decryptCipher;
public DesUtil(String password) {
try {
Key key = generateKey(password);
encryptCipher = Cipher.getInstance(TRANSFORMATION);
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
decryptCipher = Cipher.getInstance(TRANSFORMATION);
decryptCipher.init(Cipher.DECRYPT_MODE, key);
} catch (Exception e) {
throw new IllegalStateException("DES 初始化失敗", e);
}
}
private Key generateKey(String pwd) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGO);
DESKeySpec spec = new DESKeySpec(pwd.getBytes(StandardCharsets.UTF_8));
return factory.generateSecret(spec);
} catch (Exception e) {
throw new IllegalArgumentException("無效密鑰", e);
}
}
public String encrypt(String plain) {
try {
byte[] encrypted = encryptCipher.doFinal(plain.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("加密失敗", e);
}
}
public String decrypt(String cipher) {
try {
byte[] decoded = Base64.getDecoder().decode(cipher);
byte[] decrypted = decryptCipher.doFinal(decoded);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("解密失敗", e);
}
}
}
掌握標準金鑰生成方式,徹底告別「莫名其妙」的解密失敗。