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

Java 檔案加密實戰:RSA + AES 混合加密與會話金鑰管理

     編輯:Java 相關知識

企業級檔案加密方案:非對稱加密保護對稱會話金鑰完整實作

本文提供一個生產級可用的 Java 檔案加密/解密完整範例,採用 RSA-1024 非對稱加密保護 AES-256 (Rijndael) 會話金鑰 的混合加密架構,整合 PBKDF2 風格密碼加密私鑰、CBC 模式、PKCS5Padding、隨機 IV、完整資料流處理。依賴 Bouncy Castle Provider 提供 Twofish、Rijndael 等演算法支援。

架構設計

加密流程:
1. 產生 RSA 金鑰對 → 公鑰明文儲存,私鑰以密碼加密儲存 (PBEWithSHAAndTwofish-CBC)
2. 產生隨機 AES-256 會話金鑰
3. 公鑰加密會話金鑰 → 寫入檔案頭(長度 + 密文)
4. 產生隨機 16-byte IV → 寫入檔案頭
5. AES-256-CBC-PKCS5Padding 加密檔案內容 → 串流寫入

解密流程:
1. 讀取加密私鑰檔 → 密碼解密取得 RSA 私鑰
2. 讀取檔案頭:會話金鑰長度、加密會話金鑰、IV
3. 私鑰解密會話金鑰
4. AES-256-CBC 解密檔案內容 → 串流輸出

環境配置

  1. 下載 Bouncy Castle:bcprov-jdk15on-xxx.jar (JDK 1.5+)
  2. 安裝 Provider:
    • 複製到 $JAVA_HOME/jre/lib/ext/ 及 $JAVA_HOME/../jre/lib/ext/
    • 或程式碼動態註冊:Security.addProvider(new BouncyCastleProvider());
  3. 修改 java.security:security.provider.6=org.bouncycastle.jce.provider.BouncyCastleProvider

核心程式碼精簡版

import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.io.*;

public class FileEncryptor {
    private static final int ITERATIONS = 1000;
    private static final String PBE_ALGO = "PBEWithSHAAndTwofish-CBC";
    private static final String RSA_ALGO = "RSA/ECB/PKCS1Padding";
    private static final String AES_ALGO = "AES/CBC/PKCS5Padding";  // Rijndael = AES
    private static final int AES_KEY_SIZE = 256;
    private static final int IV_SIZE = 16;

    // 配置屬性(可透過 setter 注入)
    private String privateKeyFile = "private.key";
    private String plainFile = "plain.txt";
    private String encryptedFile = "encrypted.dat";
    private String decryptedFile = "decrypted.txt";
    private String password = "strongPassword";

    // --- 金鑰生成 ---
    public void generateKeys() throws Exception {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(2048);  // 建議 2048+ bits
        KeyPair kp = kpg.generateKeyPair();

        byte[] pubKeyBytes = kp.getPublic().getEncoded();
        byte[] privKeyBytes = kp.getPrivate().getEncoded();

        // 密碼加密私鑰
        byte[] encryptedPrivKey = encryptPrivateKey(password.toCharArray(), privKeyBytes);
        try (FileOutputStream fos = new FileOutputStream(privateKeyFile)) {
            fos.write(encryptedPrivKey);
        }
        // 公鑰可另存或內嵌
    }

    private byte[] encryptPrivateKey(char[] pwd, byte[] privKey) throws Exception {
        byte[] salt = new byte[8];
        new SecureRandom().nextBytes(salt);
        PBEKeySpec keySpec = new PBEKeySpec(pwd);
        SecretKeyFactory skf = SecretKeyFactory.getInstance(PBE_ALGO);
        SecretKey key = skf.generateSecret(keySpec);
        PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
        Cipher cipher = Cipher.getInstance(PBE_ALGO);
        cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
        byte[] cipherPrivKey = cipher.doFinal(privKey);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(salt);
        baos.write(cipherPrivKey);
        return baos.toByteArray();
    }

    // --- 加密檔案 ---
    public void encryptFile() throws Exception {
        // 讀取公鑰 (這裡簡化:實際應從憑證或金鑰庫讀取)
        // 假設公鑰位元組已備妥
        // X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        // PublicKey pubKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);

        // 為示範完整性,這裡重新生成一對(實際應分離生成與使用階段)
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
        kpg.initialize(2048);
        KeyPair kp = kpg.generateKeyPair();
        PublicKey pubKey = kp.getPublic();

        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(encryptedFile));
             FileInputStream fis = new FileInputStream(plainFile)) {

            // RSA 加密會話金鑰
            Cipher rsaCipher = Cipher.getInstance(RSA_ALGO);
            rsaCipher.init(Cipher.ENCRYPT_MODE, pubKey);

            KeyGenerator kg = KeyGenerator.getInstance("AES");
            kg.init(AES_KEY_SIZE);
            SecretKey sessionKey = kg.generateKey();

            byte[] encSessionKey = rsaCipher.doFinal(sessionKey.getEncoded());
            dos.writeInt(encSessionKey.length);
            dos.write(encSessionKey);

            // IV
            byte[] iv = new byte[IV_SIZE];
            new SecureRandom().nextBytes(iv);
            dos.write(iv);

            // AES 加密內容
            Cipher aesCipher = Cipher.getInstance(AES_ALGO);
            aesCipher.init(Cipher.ENCRYPT_MODE, sessionKey, new IvParameterSpec(iv));

            try (CipherOutputStream cos = new CipherOutputStream(dos, aesCipher)) {
                byte[] buf = new byte[8192];
                int len;
                while ((len = fis.read(buf)) != -1) {
                    cos.write(buf, 0, len);
                }
            }
        }
    }

    // --- 解密檔案 ---
    public void decryptFile() throws Exception {
        // 1. 讀取並解密私鑰
        byte[] encPrivKeyBytes;
        try (FileInputStream fis = new FileInputStream(privateKeyFile)) {
            encPrivKeyBytes = fis.readAllBytes(); // Java 9+
        }
        byte[] privKeyBytes = decryptPrivateKey(password.toCharArray(), encPrivKeyBytes);
        PrivateKey privKey = KeyFactory.getInstance("RSA")
                .generatePrivate(new PKCS8EncodedKeySpec(privKeyBytes));

        // 2. 解密檔案
        try (DataInputStream dis = new DataInputStream(new FileInputStream(encryptedFile));
             FileOutputStream fos = new FileOutputStream(decryptedFile)) {

            // 讀取加密會話金鑰
            byte[] encSessionKey = new byte[dis.readInt()];
            dis.readFully(encSessionKey);

            Cipher rsaCipher = Cipher.getInstance(RSA_ALGO);
            rsaCipher.init(Cipher.DECRYPT_MODE, privKey);
            byte[] sessionKeyBytes = rsaCipher.doFinal(encSessionKey);
            SecretKey sessionKey = new SecretKeySpec(sessionKeyBytes, "AES");

            // 讀取 IV
            byte[] iv = new byte[IV_SIZE];
            dis.readFully(iv);

            // AES 解密內容
            Cipher aesCipher = Cipher.getInstance(AES_ALGO);
            aesCipher.init(Cipher.DECRYPT_MODE, sessionKey, new IvParameterSpec(iv));

            try (CipherInputStream cis = new CipherInputStream(dis, aesCipher)) {
                byte[] buf = new byte[8192];
                int len;
                while ((len = cis.read(buf)) != -1) {
                    fos.write(buf, 0, len);
                }
            }
        }
    }

    private byte[] decryptPrivateKey(char[] pwd, byte[] ciphertext) throws Exception {
        byte[] salt = new byte[8];
        System.arraycopy(ciphertext, 0, salt, 0, 8);
        byte[] encPrivKey = new byte[ciphertext.length - 8];
        System.arraycopy(ciphertext, 8, encPrivKey, 0, encPrivKey.length);

        PBEKeySpec keySpec = new PBEKeySpec(pwd);
        SecretKeyFactory skf = SecretKeyFactory.getInstance(PBE_ALGO);
        SecretKey key = skf.generateSecret(keySpec);
        PBEParameterSpec paramSpec = new PBEParameterSpec(salt, ITERATIONS);
        Cipher cipher = Cipher.getInstance(PBE_ALGO);
        cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
        return cipher.doFinal(encPrivKey);
    }

    // Getter/Setter 略...
}

關鍵安全細節

項目建議風險
RSA 金鑰長度≥ 2048 bits (1024 已不安全)可被分解攻擊
對稱演算法AES-256-GCM (優先) / AES-256-CBCGCM 提供認證加密,防篡改
密碼衍生PBKDF2 / Argon2 / scrypt (高迭代、記憶體硬化)防暴力破解
隨機數SecureRandom (非 Random)可預測 IV/金鑰導致破解
完整性驗證加密後計算 HMAC-SHA256 或使用 GCM 模式防止密文竄改攻擊
金鑰管理硬體安全模組 (HSM) / 金鑰庫 / 密碼管理器私鑰洩漏 = 全盤崩潰
例外處理不吞沒例外,記錄審計日誌靜默失敗掩蓋攻擊

現代化替代方案

  • Java 內建:java.security、javax.crypto (JDK 1.4+ 支援 AES,無需 Bouncy Castle)
  • 高階庫:Google Tink、Bouncy Castle 高階 API、Apache Shiro Crypto
  • 標準格式:JWE (JSON Web Encryption)、CMS/PKCS#7、OpenPGP
  • 檔案加密工具:age (現代、簡單)、GPG、VeraCrypt (磁碟級)

小結

混合加密(非對稱保護對稱金鑰)是大檔案加密的標準架構。本範例展示完整流程:金鑰生成、密碼保護私鑰、會話金鑰管理、串流加解密。生產部署務必:升級金鑰長度、採用認證加密 (GCM)、強化密碼衍生、引入完整性驗證、妥善管理金鑰生命週期。建議優先使用成熟庫(Tink、age)而非自行拼裝密碼學原語。

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