3DES (Triple DES,JCA 算法名 DESede) 是 DES 的增強版,透過三次 DES 運算提供 112 位有效安全強度。雖然現代應用建議改用 AES,但 3DES 在金融、舊系統整合、特定合規場景仍廣泛使用。本文提供生產級完整實作,含金鑰管理、Base64 編碼、異常處理與最佳實務。
| 參數 | 值/說明 |
|---|---|
| 演算法名稱 | DESede (JCA 標準名) |
| 金鑰長度 | 168 位 (24 bytes),含 8 位奇偶校驗位,有效 112 位 |
| 區塊大小 | 64 位 (8 bytes) |
| 模式/填充 | DESede/CBC/PKCS5Padding (推薦) |
| 初始向量 (IV) | CBC 模式必須,8 bytes,需隨機產生 |
package com.example.crypto;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.Base64;
public final class TripleDesUtil {
private static final String ALGORITHM = "DESede";
private static final String TRANSFORMATION = "DESede/CBC/PKCS5Padding";
private static final int KEY_SIZE_BYTES = 24; // 168 bits
private static final int IV_SIZE_BYTES = 8; // 64 bits
private TripleDesUtil() {} // 工具類防實例化
/**
* 從十六進制字串或 Base64 字串載入金鑰
* @param keyHexOrBase64 24 bytes 金鑰的 Hex 或 Base64 編碼
* @return SecretKeySpec
*/
public static SecretKeySpec loadKey(String keyHexOrBase64) {
byte[] keyBytes;
try {
// 嘗試 Hex 解碼
if (keyHexOrBase64.matches("[0-9A-Fa-f]+")) {
keyBytes = hexToBytes(keyHexOrBase64);
} else {
// 嘗試 Base64 解碼
keyBytes = Base64.getDecoder().decode(keyHexOrBase64);
}
} catch (Exception e) {
throw new IllegalArgumentException("金鑰格式錯誤,需 24 bytes Hex 或 Base64", e);
}
if (keyBytes.length != KEY_SIZE_BYTES) {
throw new IllegalArgumentException("3DES 金鑰必須為 24 bytes (168 bits),實際: " + keyBytes.length);
}
return new SecretKeySpec(keyBytes, ALGORITHM);
}
/**
* 加密:回傳 Base64 編碼的 "IV + Ciphertext"
* @param plaintext 明文字串
* @param key SecretKeySpec
* @return Base64(IV || Ciphertext)
*/
public static String encrypt(String plaintext, SecretKeySpec key) {
try {
// 1. 產生隨機 IV
byte[] iv = new byte[IV_SIZE_BYTES];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// 2. 初始化 Cipher
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
// 3. 加密
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 4. 組合 IV + Ciphertext 並 Base64 編碼
byte[] combined = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(ciphertext, 0, combined, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(combined);
} catch (GeneralSecurityException e) {
throw new CryptoException("加密失敗", e);
}
}
/**
* 解密:輸入 Base64(IV || Ciphertext)
*/
public static String decrypt(String combinedBase64, SecretKeySpec key) {
try {
byte[] combined = Base64.getDecoder().decode(combinedBase64);
if (combined.length < IV_SIZE_BYTES) {
throw new IllegalArgumentException("密文長度不足,缺少 IV");
}
// 1. 分離 IV 與 Ciphertext
byte[] iv = new byte[IV_SIZE_BYTES];
byte[] ciphertext = new byte[combined.length - IV_SIZE_BYTES];
System.arraycopy(combined, 0, iv, 0, IV_SIZE_BYTES);
System.arraycopy(combined, IV_SIZE_BYTES, ciphertext, 0, ciphertext.length);
// 2. 初始化 Cipher
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
// 3. 解密
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext, StandardCharsets.UTF_8);
} catch (GeneralSecurityException e) {
throw new CryptoException("解密失敗", e);
}
}
// 輔助:Hex 字串轉 byte[]
private static byte[] hexToBytes(String hex) {
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return data;
}
// 自訂異常
public static class CryptoException extends RuntimeException {
public CryptoException(String msg, Throwable cause) { super(msg, cause); }
}
}
public class TripleDesDemo {
public static void main(String[] args) {
// 24 bytes 金鑰 (Hex 格式,實際應從安全配置中心載入)
String keyHex = "11224F588810403828257951CBDD556677297498304036E2";
SecretKeySpec key = TripleDesUtil.loadKey(keyHex);
String original = "這是一個 3DES 加密測試:中文、English、12345、特殊符號!@#$%";
System.out.println("原文: " + original);
// 加密
String encrypted = TripleDesUtil.encrypt(original, key);
System.out.println("密文 (Base64): " + encrypted);
// 解密
String decrypted = TripleDesUtil.decrypt(encrypted, key);
System.out.println("解密: " + decrypted);
// 驗證
System.out.println("驗證: " + original.equals(decrypted));
}
}
| 實務 | 說明 |
|---|---|
| 金鑰管理 | 嚴禁硬編碼。使用 Vault、AWS KMS、Azure Key Vault、HashiCorp Vault 等密管系統;或環境變數 + 檔案權限 600 |
| IV 隨機性 | 每次加密必須使用 SecureRandom 產生新 IV,不可重複使用 |
| 模式選擇 | 嚴禁 ECB 模式 (無 IV,相同明文產生相同密文);生產環境強制 CBC 或 GCM (需 AES) |
| 填充模式 | 使用 PKCS5Padding (JCE 對 3DES 實際為 PKCS7),避免自行實作填充 |
| 完整性保護 | 3DES-CBC 無完整性驗證,易受填充神諭攻擊。高安全場景需額外 HMAC-SHA256 或改用 AES-GCM |
| 金鑰輪換 | 定期輪換金鑰,舊密文需保留舊金鑰解密能力 |
InvalidKeyException: Wrong key size:金鑰非 24 bytes。檢查 Hex/Base64 解碼後長度。BadPaddingException:金鑰錯誤、IV 錯誤、資料截斷、或 ECB/CBC 模式不匹配。IllegalBlockSizeException:輸入資料長度非 8 bytes 倍數 (無填充模式時);使用 PKCS5Padding 可避免。getBytes() 必須指定 StandardCharsets.UTF_8,不可依賴平台預設編碼。
// AES-256-GCM 現代標準 (Java 8+)
public static String encryptAesGcm(String plaintext, SecretKey key) {
byte[] iv = new byte[12]; // GCM 建議 96-bit IV
new SecureRandom().nextBytes(iv);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 組合: IV (12) + Ciphertext + AuthTag (16, 已附在 ciphertext 末尾)
byte[] combined = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(ciphertext, 0, combined, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(combined);
}
3DES (DESede) 透過 CBC/PKCS5Padding + 隨機 IV + Base64(IV||Ciphertext) 可構建相對安全的加密方案。但受限於 64-bit 區塊大小 (Sweet32 攻擊風險) 與 112-bit 有效強度,**新系統嚴禁使用 3DES,強制採用 AES-256-GCM 或 ChaCha20-Poly1305**。維護舊系統時,務必嚴格執行金鑰管理、IV 隨機化、完整性保護 (HMAC) 等補強措施。