本文提供一個生產級可用的 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 解密檔案內容 → 串流輸出
bcprov-jdk15on-xxx.jar (JDK 1.5+)$JAVA_HOME/jre/lib/ext/ 及 $JAVA_HOME/../jre/lib/ext/Security.addProvider(new BouncyCastleProvider());java.security:security.provider.6=org.bouncycastle.jce.provider.BouncyCastleProviderimport 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-CBC | GCM 提供認證加密,防篡改 |
| 密碼衍生 | PBKDF2 / Argon2 / scrypt (高迭代、記憶體硬化) | 防暴力破解 |
| 隨機數 | SecureRandom (非 Random) | 可預測 IV/金鑰導致破解 |
| 完整性驗證 | 加密後計算 HMAC-SHA256 或使用 GCM 模式 | 防止密文竄改攻擊 |
| 金鑰管理 | 硬體安全模組 (HSM) / 金鑰庫 / 密碼管理器 | 私鑰洩漏 = 全盤崩潰 |
| 例外處理 | 不吞沒例外,記錄審計日誌 | 靜默失敗掩蓋攻擊 |
java.security、javax.crypto (JDK 1.4+ 支援 AES,無需 Bouncy Castle)混合加密(非對稱保護對稱金鑰)是大檔案加密的標準架構。本範例展示完整流程:金鑰生成、密碼保護私鑰、會話金鑰管理、串流加解密。生產部署務必:升級金鑰長度、採用認證加密 (GCM)、強化密碼衍生、引入完整性驗證、妥善管理金鑰生命週期。建議優先使用成熟庫(Tink、age)而非自行拼裝密碼學原語。