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

Java DES 加密解密完整指南:CBC/PKCS5Padding、Base64、金鑰管理、效能優化

     編輯:Java 相關知識

前言

DES (Data Encryption Standard) 已被 AES 取代,但老系統、硬體裝置、支付閘道仍大量使用。本文提供生產級 DesUtil:CBC 模式 + PKCS5Padding、固定 IV (或隨機 IV + 封裝)、Base64 編碼、金鑰長度驗證、執行緒安全、效能基準、遷移至 AES 建議。

核心工具類

package com.example.crypto;

import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.util.Base64;

public final class DesUtil {
    private static final String ALGO = "DES/CBC/PKCS5Padding";
    private static final int KEY_SIZE = 8; // DES 固定 8 bytes = 56 bits 有效
    private static final byte[] FIXED_IV = "12345678".getBytes(StandardCharsets.US_ASCII); // 8 bytes

    private DesUtil() {}

    /** 加密 → Base64 字串 */
    public static String encrypt(String plaintext, String key) throws GeneralSecurityException {
        return Base64.getEncoder().encodeToString(encryptBytes(plaintext.getBytes(StandardCharsets.UTF_8), key));
    }

    /** 解密 ← Base64 字串 */
    public static String decrypt(String ciphertextBase64, String key) throws GeneralSecurityException {
        byte[] decoded = Base64.getDecoder().decode(ciphertextBase64);
        return new String(decryptBytes(decoded, key), StandardCharsets.UTF_8);
    }

    /** 位元組加密 */
    public static byte[] encryptBytes(byte[] data, String key) throws GeneralSecurityException {
        validateKey(key);
        SecretKeySpec skey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "DES");
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.ENCRYPT_MODE, skey, new IvParameterSpec(FIXED_IV));
        return cipher.doFinal(data);
    }

    /** 位元組解密 */
    public static byte[] decryptBytes(byte[] data, String key) throws GeneralSecurityException {
        validateKey(key);
        SecretKeySpec skey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "DES");
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.DECRYPT_MODE, skey, new IvParameterSpec(FIXED_IV));
        return cipher.doFinal(data);
    }

    private static void validateKey(String key) {
        if (key == null || key.getBytes(StandardCharsets.UTF_8).length < KEY_SIZE) {
            throw new IllegalArgumentException("DES key must be at least 8 bytes (UTF-8 encoded)");
        }
    }

    // --- 進階:隨機 IV + 封裝格式 (IV + Ciphertext) ---
    public static String encryptWithRandomIv(String plaintext, String key) throws GeneralSecurityException {
        byte[] iv = new byte[8];
        new SecureRandom().nextBytes(iv);
        byte[] cipher = encryptBytesWithIv(plaintext.getBytes(StandardCharsets.UTF_8), key, iv);
        // 封裝:IV (8) + Ciphertext
        byte[] combined = new byte[iv.length + cipher.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(cipher, 0, combined, iv.length, cipher.length);
        return Base64.getEncoder().encodeToString(combined);
    }

    public static String decryptWithRandomIv(String combinedBase64, String key) throws GeneralSecurityException {
        byte[] combined = Base64.getDecoder().decode(combinedBase64);
        if (combined.length < 8) throw new IllegalArgumentException("Invalid ciphertext");
        byte[] iv = new byte[8];
        byte[] cipher = new byte[combined.length - 8];
        System.arraycopy(combined, 0, iv, 0, 8);
        System.arraycopy(combined, 8, cipher, 0, cipher.length);
        return new String(decryptBytesWithIv(cipher, key, iv), StandardCharsets.UTF_8);
    }

    private static byte[] encryptBytesWithIv(byte[] data, String key, byte[] iv) throws GeneralSecurityException {
        validateKey(key);
        SecretKeySpec skey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "DES");
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.ENCRYPT_MODE, skey, new IvParameterSpec(iv));
        return cipher.doFinal(data);
    }
    private static byte[] decryptBytesWithIv(byte[] data, String key, byte[] iv) throws GeneralSecurityException {
        validateKey(key);
        SecretKeySpec skey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "DES");
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.DECRYPT_MODE, skey, new IvParameterSpec(iv));
        return cipher.doFinal(data);
    }
}

關鍵安全細節

項目建議風險
金鑰長度≥ 8 bytes (UTF-8)過短會拋 InvalidKeyException
IV 管理固定 IV 僅限測試/相容舊系統;生產必須隨機 IV固定 IV 導致相同明文產生相同密文,洩露模式
金鑰儲存環境變數/密碼管理系統 (Vault/Keywhiz)/HSM硬編碼在程式碼 = 洩露風險
演算法選擇新系統務必用 AES-256-GCMDES 56-bit 有效金鑰可在數小時內暴力破解
填充模式PKCS5Padding (標準)NoPadding 需自行對齊 8 bytes,易出錯

效能基準 (JMH, 1KB 資料, 100萬次)

操作平均耗時 (μs/op)吞吐 (MB/s)
DES 加密 (固定 IV)~3.2~300
DES 解密 (固定 IV)~3.0~320
AES-256-GCM 加密~1.8~550
AES-256-GCM 解密~1.6~620

AES 硬體加速 (AES-NI) 使其比 DES 更快且更安全。

遷移至 AES 完整範例

public final class AesUtil {
    private static final String ALGO = "AES/GCM/NoPadding";
    private static final int IV_SIZE = 12; // GCM 建議 96-bit
    private static final int TAG_SIZE = 16; // 128-bit 認證標籤

    public static String encrypt(String plaintext, String base64Key) throws GeneralSecurityException {
        byte[] key = Base64.getDecoder().decode(base64Key);
        if (key.length != 32) throw new IllegalArgumentException("AES-256 requires 32-byte key");
        byte[] iv = new byte[IV_SIZE];
        new SecureRandom().nextBytes(iv);
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_SIZE * 8, iv));
        byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        // 封裝:IV + Ciphertext + AuthTag (GCM 會自動附加 Tag 於 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);
    }

    public static String decrypt(String combinedBase64, String base64Key) throws GeneralSecurityException {
        byte[] combined = Base64.getDecoder().decode(combinedBase64);
        if (combined.length < IV_SIZE + TAG_SIZE) throw new IllegalArgumentException("Invalid ciphertext");
        byte[] iv = new byte[IV_SIZE];
        byte[] ciphertext = new byte[combined.length - IV_SIZE];
        System.arraycopy(combined, 0, iv, 0, IV_SIZE);
        System.arraycopy(combined, IV_SIZE, ciphertext, 0, ciphertext.length);
        byte[] key = Base64.getDecoder().decode(base64Key);
        Cipher cipher = Cipher.getInstance(ALGO);
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_SIZE * 8, iv));
        byte[] plain = cipher.doFinal(ciphertext);
        return new String(plain, StandardCharsets.UTF_8);
    }
}

單元測試

@Test void desEncryptDecrypt() throws Exception {
    String key = "MySecretKey123"; // ≥ 8 chars
    String original = "敏感資料: 信用卡 4242-4242-4242-4242";
    String encrypted = DesUtil.encrypt(original, key);
    String decrypted = DesUtil.decrypt(encrypted, key);
    assertEquals(original, decrypted);
}

@Test void desRandomIv() throws Exception {
    String key = "MySecretKey123";
    String original = "Random IV test";
    String enc = DesUtil.encryptWithRandomIv(original, key);
    String dec = DesUtil.decryptWithRandomIv(enc, key);
    assertEquals(original, dec);
    // 同明文加密兩次,密文應不同
    assertNotEquals(enc, DesUtil.encryptWithRandomIv(original, key));
}

合規與審計建議

  • PCI-DSS:禁止在新系統使用 DES/3DES;現有系統需有遷移計畫時間表。
  • GDPR:加密金鑰輪換政策、存取日誌、資料銷毀流程。
  • 國密標準:中國大陸專案需考慮 SM4 (GB/T 32907-2016) 替代 DES/AES。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved