Java Cryptography Extension (JCE) 內建 `SunJCE` 提供者,支援 DES、3DES (DESede)、AES、Blowfish 等對稱加密算法。本文展示零第三方依賴、純 JDK API 的 3DES 加解密標準實作,含金鑰生成、模式選擇、IV 處理、十六進位顯示與完整測試代碼。
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.*;
import java.util.Arrays;
public class Standard3DES {
// 算法常數
private static final String ALGORITHM = "DESede"; // 3DES
private static final String TRANSFORMATION_ECB = "DESede/ECB/PKCS5Padding";
private static final String TRANSFORMATION_CBC = "DESede/CBC/PKCS5Padding";
private static final int KEY_SIZE = 24; // 3DES 金鑰 24 bytes
/**
* 3DES 加密 (ECB 模式,無 IV)
* @param keyBytes 24 位元組金鑰
* @param plainData 明文資料
* @return 密文位元組陣列
*/
public static byte[] encryptECB(byte[] keyBytes, byte[] plainData) {
return crypt(TRANSFORMATION_ECB, Cipher.ENCRYPT_MODE, keyBytes, null, plainData);
}
/**
* 3DES 解密 (ECB 模式)
*/
public static byte[] decryptECB(byte[] keyBytes, byte[] cipherData) {
return crypt(TRANSFORMATION_ECB, Cipher.DECRYPT_MODE, keyBytes, null, cipherData);
}
/**
* 3DES 加密 (CBC 模式,需 IV) - 推薦生產環境
* @param keyBytes 24 位元組金鑰
* @param iv 8 位元組初始化向量
* @param plainData 明文資料
*/
public static byte[] encryptCBC(byte[] keyBytes, byte[] iv, byte[] plainData) {
return crypt(TRANSFORMATION_CBC, Cipher.ENCRYPT_MODE, keyBytes, iv, plainData);
}
/**
* 3DES 解密 (CBC 模式)
*/
public static byte[] decryptCBC(byte[] keyBytes, byte[] iv, byte[] cipherData) {
return crypt(TRANSFORMATION_CBC, Cipher.DECRYPT_MODE, keyBytes, iv, cipherData);
}
/**
* 核心加解密邏輯
*/
private static byte[] crypt(String transformation, int mode, byte[] keyBytes, byte[] iv, byte[] data) {
try {
// 1. 驗證金鑰長度
if (keyBytes == null || keyBytes.length != KEY_SIZE) {
throw new IllegalArgumentException("3DES 金鑰必須為 24 位元組,實際: " +
(keyBytes == null ? "null" : keyBytes.length));
}
// 2. 建立 SecretKey
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, ALGORITHM);
// 3. 取得 Cipher 實例
Cipher cipher = Cipher.getInstance(transformation);
// 4. 初始化
if (iv != null) {
if (iv.length != 8) throw new IllegalArgumentException("3DES IV 必須為 8 位元組");
cipher.init(mode, keySpec, new IvParameterSpec(iv));
} else {
cipher.init(mode, keySpec);
}
// 5. 執行
return cipher.doFinal(data);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new CryptoException("3DES 操作失敗: " + transformation, e);
}
}
/**
* 位元組陣列轉十六進位字串 (含分隔符,便於閱讀)
*/
public static String bytesToHex(byte[] bytes) {
if (bytes == null) return "null";
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) sb.append('0');
sb.append(hex);
if (i < bytes.length - 1) sb.append(':');
}
return sb.toUpperCase();
}
// 自定義異常
public static class CryptoException extends RuntimeException {
public CryptoException(String msg, Throwable cause) { super(msg, cause); }
}
// ========== 測試主程式 ==========
public static void main(String[] args) {
System.out.println("=== JDK SunJCE 3DES 標準測試 ===\n");
// 1. 準備 24 位元組金鑰 (實際應從安全隨機生成或 KDF 派生)
byte[] key = new byte[] {
0x11, 0x22, 0x4F, 0x58, (byte)0x88, 0x10, 0x40, 0x38,
0x28, 0x25, 0x79, 0x51, (byte)0xCB, (byte)0xDD, 0x55, 0x66,
0x77, 0x29, 0x74, (byte)0x98, 0x30, 0x40, 0x36, (byte)0xE2
};
// 2. CBC 模式需 8 位元組 IV
byte[] iv = new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
// 3. 測試資料 (含中文)
String plainText = "This is a 3DES test. 測試中文數據";
byte[] plainBytes = plainText.getBytes(StandardCharsets.UTF_8);
System.out.println("原文: " + plainText);
System.out.println("原文 Hex: " + bytesToHex(plainBytes));
System.out.println("金鑰: " + bytesToHex(key));
System.out.println("IV: " + bytesToHex(iv));
System.out.println();
// 4. ECB 模式測試
System.out.println("--- ECB 模式 ---");
byte[] ecbEncrypted = encryptECB(key, plainBytes);
System.out.println("ECB 密文: " + bytesToHex(ecbEncrypted));
byte[] ecbDecrypted = decryptECB(key, ecbEncrypted);
System.out.println("ECB 解密: " + new String(ecbDecrypted, StandardCharsets.UTF_8));
System.out.println("ECB 驗證: " + (Arrays.equals(plainBytes, ecbDecrypted) ? "✅ 通過" : "❌ 失敗"));
System.out.println();
// 5. CBC 模式測試 (推薦)
System.out.println("--- CBC 模式 (推薦) ---");
byte[] cbcEncrypted = encryptCBC(key, iv, plainBytes);
System.out.println("CBC 密文: " + bytesToHex(cbcEncrypted));
byte[] cbcDecrypted = decryptCBC(key, iv, cbcEncrypted);
System.out.println("CBC 解密: " + new String(cbcDecrypted, StandardCharsets.UTF_8));
System.out.println("CBC 驗證: " + (Arrays.equals(plainBytes, cbcDecrypted) ? "✅ 通過" : "❌ 失敗"));
}
}
| 特性 | ECB (Electronic Codebook) | CBC (Cipher Block Chaining) |
|---|---|---|
| IV 需求 | 不需要 | 需要 8 bytes (3DES 區塊大小) |
| 相同明文塊 | 產生相同密文塊 ❌ 洩漏模式 | 產生不同密文塊 ✅ |
| 並行計算 | 可並行 | 加密串行,解密可並行 |
| 錯誤傳播 | 單塊 | 影響當前塊及下一塊 |
| 安全性 | 低 (不推薦) | 標準推薦 |
| 適用場景 | 極短資料、測試、舊系統相容 | **生產環境標準** |
// ❌ 硬編碼金鑰 (僅測試用)
// ✅ 生產環境:從密碼派生 (PBKDF2) 或安全隨機生成
// 方案一:PBKDF2 派生 (密碼基礎)
public static byte[] deriveKeyPBKDF2(String password, byte[] salt, int iterations) {
try {
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, 192); // 192 bits = 24 bytes
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
return factory.generateSecret(spec).getEncoded();
} catch (Exception e) {
throw new CryptoException("PBKDF2 失敗", e);
}
}
// 方案二:安全隨機生成 (密鑰交換場景)
public static byte[] generateRandomKey() {
byte[] key = new byte[24];
new SecureRandom().nextBytes(key);
// 修正奇偶校驗位 (DESedeKeySpec 會自動處理,但手動生成時需注意)
return key;
}
| 異常 | 常見原因 | 解決 |
|---|---|---|
| InvalidKeyException: Wrong key size | 金鑰非 24 bytes | 檢查金鑰生成/派生邏輯,確保 24 bytes |
| InvalidAlgorithmParameterException: Wrong IV length | IV 非 8 bytes | CBC 模式固定 8 bytes IV |
| BadPaddingException | 金鑰錯誤、資料截斷、填充損壞 | 驗證金鑰一致性、資料完整傳輸 |
| NoSuchAlgorithmException: DESede | JCE 提供者未載入 | 確認 `Security.getProviders()` 含 SunJCE,或手動 `Security.addProvider(new SunJCE())` |
// 僅需變更常數,代碼結構完全相容
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION_CBC = "AES/CBC/PKCS5Padding";
private static final int KEY_SIZE = 16; // 或 24, 32 (AES-128/192/256)
private static final int IV_SIZE = 16; // AES 區塊大小 128-bit = 16 bytes
// 金鑰生成
byte[] aesKey = new byte[16]; // 128-bit
new SecureRandom().nextBytes(aesKey);
// 或 PBKDF2 派生 128/192/256 bits
JDK 內建 `SunJCE` 提供完整、標準、零依賴的加密能力。掌握標準 API、正確選擇模式/填充、妥善管理金鑰/IV、處理編碼邊界,是 Java 後端安全開發的核心基本功。