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

Java AES 對稱加密完整實戰:GCM 模式、密鑰管理、PBKDF2、Spring Boot 整合與安全最佳實踐

     編輯:Java 相關知識

為什麼選 AES-GCM?

核心參數規範

  • 密鑰長度:128 位 (足夠安全) / 256 位 (長期機密、合規要求)
  • IV/Nonce 長度:12 字節 (96 位) (GCM 標準、性能最佳),**每次加密必須唯一** (隨機生成或計數器)。
  • 認證標簽 Tag:16 字節 (128 位),驗證密文完整性與關聯數據。
  • 關聯數據 AAD:不加密但需認證的數據 (如协議版本、用戶 ID、時間戳)。

生產級工具類 (AES-GCM + PBKDF2 密鑰派生)

public final class AesGcmCrypto {
    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int IV_LENGTH = 12;      // 96 bits
    private static final int TAG_LENGTH = 16;     // 128 bits
    private static final int KEY_LENGTH = 256;    // 256 bits
    private static final int PBKDF2_ITERATIONS = 100_000; // OWASP 建議 ≥ 100k
    private static final int SALT_LENGTH = 16;    // 128 bits

    private AesGcmCrypto() {}

    /**
     * 從密碼派生密鑰 (PBKDF2WithHmacSHA256)
     * @param password 用戶密碼/主密鑰
     * @param salt 隨機鹽值 (存儲與密文一起)
     * @return SecretKey
     */
    public static SecretKey deriveKey(char[] password, byte[] salt) {
        try {
            PBEKeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, KEY_LENGTH);
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            byte[] keyBytes = factory.generateSecret(spec).getEncoded();
            return new SecretKeySpec(keyBytes, "AES");
        } catch (Exception e) {
            throw new CryptoException("密鑰派生失敗", e);
        }
    }

    /**
     * 加密:返回 Base64 編碼的 [Salt(16) + IV(12) + CipherText + Tag(16)]
     */
    public static String encrypt(String plaintext, char[] password) {
        try {
            // 1. 生成隨機鹽值
            byte[] salt = SecureRandom.getInstanceStrong().generateSeed(SALT_LENGTH);
            
            // 2. 派生密鑰
            SecretKey key = deriveKey(password, salt);
            
            // 3. 生成隨機 IV
            byte[] iv = SecureRandom.getInstanceStrong().generateSeed(IV_LENGTH);
            
            // 4. 初始化 Cipher
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH * 8, iv);
            cipher.init(Cipher.ENCRYPT_MODE, key, spec);
            
            // 5. 可選 AAD (如版本號)
            byte[] aad = "v1".getBytes(StandardCharsets.UTF_8);
            cipher.updateAAD(aad);
            
            // 6. 加密 (輸出包含 Tag)
            byte[] cipherText = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
            
            // 7. 組裝: Salt + IV + CipherText
            ByteBuffer buffer = ByteBuffer.allocate(salt.length + iv.length + cipherText.length);
            buffer.put(salt).put(iv).put(cipherText);
            
            return Base64.getEncoder().encodeToString(buffer.array());
        } catch (Exception e) {
            throw new CryptoException("加密失敗", e);
        }
    }

    /**
     * 解密:輸入 Base64 [Salt + IV + CipherText]
     */
    public static String decrypt(String ciphertextBase64, char[] password) {
        try {
            byte[] decoded = Base64.getDecoder().decode(ciphertextBase64);
            ByteBuffer buffer = ByteBuffer.wrap(decoded);
            
            // 1. 解析 Salt
            byte[] salt = new byte[SALT_LENGTH];
            buffer.get(salt);
            
            // 2. 解析 IV
            byte[] iv = new byte[IV_LENGTH];
            buffer.get(iv);
            
            // 3. 剩餘為密文 (含 Tag)
            byte[] cipherText = new byte[buffer.remaining()];
            buffer.get(cipherText);
            
            // 4. 派生密鑰
            SecretKey key = deriveKey(password, salt);
            
            // 5. 初始化 Cipher
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH * 8, iv);
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD("v1".getBytes(StandardCharsets.UTF_8));
            
            // 6. 解密驗證 (Tag 驗證失敗拋 AEADBadTagException)
            byte[] plainBytes = cipher.doFinal(cipherText);
            
            return new String(plainBytes, StandardCharsets.UTF_8);
        } catch (AEADBadTagException e) {
            throw new CryptoException("認證失敗:密文被篡改或密鑰錯誤", e);
        } catch (Exception e) {
            throw new CryptoException("解密失敗", e);
        }
    }

    /** 高級:使用隨機生成的數據加密密鑰 (DEK) + 密鑰加密密鑰 (KEK) 分層 */
    public static EncryptedData encryptWithDek(String plaintext, SecretKey kek) {
        // 1. 生成隨機 DEK
        SecretKey dek = generateRandomKey();
        // 2. DEK 加密數據
        String encryptedData = encryptWithKey(plaintext, dek);
        // 3. KEK 加密 DEK (Key Wrap: AESWrap 或 RSA-OAEP)
        String encryptedDek = wrapKey(dek, kek);
        return new EncryptedData(encryptedData, encryptedDek);
    }
    // ... 內部輔助方法省略
}

密鑰管理最佳實踐

  1. 分層加密:數據加密密鑰 (DEK) 隨機生成、定期輪換;密鑰加密密鑰 (KEK) 存儲於 HSM/KMS/Vault,極少輪換。
  2. 密鑰輪換:定期 (如 90 天) 生成新 DEK 重新加密熱數據;舊 DEK 保留解密歷史數據,標記過期。
  3. 存儲隔離:密鑰**絕不**硬編碼、配置文件、代碼庫。使用 HashiCorp Vault、AWS KMS、Azure Key Vault、Google Cloud KMS、阿里云 KMS。
  4. 訪問控制:最小權限原則,應用只能請求解密特定上下文的密鑰,審計所有解密操作。
  5. 銷毀機制:內存中密鑰用完即清零 (Arrays.fill(keyBytes, (byte)0)),JDK 9+ 用 SecretKey.destroy()。

Spring Boot 整合配置

# application.yml
crypto:
  aes-gcm:
    password: ${CRYPTO_PASSWORD:}  # 從環境變量/密管平台注入,非配置文件
    pbkdf2-iterations: 200000      # 生產環境建議 ≥ 200k
    aad: "myapp-v1"                # 關聯數據

@Component
@ConfigurationProperties(prefix = "crypto.aes-gcm")
@Data
public class CryptoProperties {
    private char[] password;
    private int pbkdf2Iterations = 100_000;
    private String aad;
}

@Service
@RequiredArgsConstructor
public class CryptoService {
    private final CryptoProperties props;

    public String encrypt(String plaintext) {
        return AesGcmCrypto.encrypt(plaintext, props.getPassword());
    }

    public String decrypt(String ciphertext) {
        return AesGcmCrypto.decrypt(ciphertext, props.getPassword());
    }
}

常見安全陷阱

模式機密性完整性/認證性能推薦度
ECB✅❌ (無 IV、塊獨立)快🚫 禁用
CBC✅❌ (需額外 HMAC)中⚠️ 僅存量遷移
CTR✅❌ (需額外 HMAC)快 (並行)⚠️ 僅特定場景
GCM✅✅ (AEAD: Associated Data + Tag)快 (硬件加速 AES-NI)✅ 首選
陷阱後果修正
硬編碼密鑰/IV代碼洩露即數據洩露KMS/Vault/環境變量注入
固定 IV (如全 0)相同明文產生相同密文,語義洩露、重放攻擊每次加密 **SecureRandom 生成 12 字節隨機 IV**
ECB 模式模式可見、塊可重排**禁用 ECB**,用 GCM
CBC 無 HMAC填充預言機攻擊、密文可篡改改 GCM 或 CBC+HMAC (Encrypt-then-MAC)
密碼直接作密鑰熵不足、字典攻擊**PBKDF2/Argon2/bcrypt/scrypt** 派生,高迭代次數
Base64 當加密零安全性Base64 僅編碼,非加密
Tag 截斷/忽略失去完整性保護**完整 16 字節 Tag**,驗證失敗拒絕解密

性能優化

  • AES-NI 硬件加速:現代 CPU (Intel Westmere+、AMD Bulldozer+) 內建 AES 指令集,JDK 自動啟用,GCM 吞吐可達 **GB/s 級別**。
  • 複用 Cipher 對象:Cipher 非線程安全,但可用 ThreadLocal 或對象池複用 (需 init 重置)。
  • 批量加密:大文件分塊流式加密 (CipherInputStream/CipherOutputStream),避免全量載入內存。

總結

**AES-GCM + PBKDF2 + KMS 密鑰管理** 是現代 Java 對稱加密的黃金標準。核心原則:**AEAD 模式 (GCM)、隨機 IV、完整 Tag、密鑰派生、分層密鑰、硬件加速、審計日誌、零硬編碼**。封裝 AesGcmCrypto 工具類,業務層零感知安全,定期輪換 DEK,接入 KMS 管理 KEK,即可構建合規、高性能的加密體系。

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