首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 3DES (DESede) 加密解密完整實作指南

     編輯:Java 相關知識

前言

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,不可依賴平台預設編碼。

遷移建議:從 3DES 到 AES


// 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) 等補強措施。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved