本文要介紹的是基于 Java 語言實現(xiàn) AES256 加解密文件功能,主要流程包括
- 讀取文件明文數(shù)據(jù),通過 AES256 加密算法進行加密,將加密后的數(shù)據(jù)寫回文件
-
讀取文件密文數(shù)據(jù),通過 AES256 解密算法進行解密,將解密后的數(shù)據(jù)寫回文件
AES 文件加解密流程.png
AES256 算法簡介
AES(高級加密標(biāo)準(zhǔn),Advanced Encryption Standard),對稱加密算法,不同于 RSA 等非對稱加密,其只使用一個密鑰參與加密和解密。
密鑰
AES256 中的 256 代表的是密鑰的長度為 256位,此外還存在 AES128、AES192,AES256 的安全性最高,AES128性能最高,本質(zhì)原因是它們的加密處理輪數(shù)不同。
填充
AES 算法在對明文加密的時候,并不是直接對明文數(shù)據(jù)進行加密,而是將明文拆分成一個一個獨立的明文段,每一段長度為 128bit。然后這些明文段經(jīng)過 AES 加密器處理,生成密文段,將密文段合并到一起,得到加密結(jié)果。
因為明文段是按照 128bit 長度進行拆分,就會存在長度不足 128bit 的情況,所以需要對不足的明文段進行填充。
Nopadding
不做任何填充,但是要求銘文必須是 16字節(jié) 的整數(shù)倍。
PKCS5Padding
不足的明文段,在末尾補足相應(yīng)數(shù)量的字符,且每個字節(jié)的值等于缺少的字符數(shù)。
ISO10126Padding
不足的明文段,在末尾補足相應(yīng)數(shù)量的字符,最后一個字符值等于缺少的字符數(shù),其他字符值填充隨機數(shù)。
模式
AES 的工作模式,分為 ECB、CBC、CTR、CFB、OFB。本文使用 CBC 模式,該模式會使用一個初始向量 IV,加密的時候,第一個明文段會首先和初始向量 IV 做異或操作,然后再經(jīng)過密鑰加密,然后第一個密文塊又會作為第二個明文段的加密向量來異或,依次類推下去,這樣相同的明文段加密出來的密文塊就是不同的,因此更加安全。
文件加密
本文主要介紹媒體文件的加密,所以不需要對文件的全部數(shù)據(jù)進行加密,僅僅加密頭部部分?jǐn)?shù)據(jù)即可,因為沒有頭部數(shù)據(jù),這些媒體文件也是無法成功進行解碼。使用部分?jǐn)?shù)據(jù)進行加密,從而提高加密性能。
public static int encryptFile(File file, SecretKey secretKey, int encryptLength) {
try {
// 以 byte 的形式讀取,不改變文件數(shù)據(jù)的編碼格式
byte[] bytes = Files.readAllBytes(file.toPath());
// 僅加密 encryptLength 長度的數(shù)據(jù)
byte[] substring = new byte[encryptLength];
System.arraycopy(bytes, 0, substring, 0, encryptLength);
// 加密
byte[] encrypt = encrypt(substring, secretKey);
// 使用密文替換老數(shù)據(jù)
byte[] newContent = new byte[encrypt.length + bytes.length - encryptLength];
System.arraycopy(encrypt, 0, newContent, 0, encrypt.length);
System.arraycopy(bytes, encryptLength, newContent, encrypt.length, bytes.length - encryptLength);
// 覆蓋寫入文件
Files.write(file.toPath(), newContent);
return encrypt.length;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
加密
private static byte[] encrypt(byte[] content, SecretKey secretKey) {
byte[] str = null;
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
str = cipher.doFinal(content);
} catch (Exception e) {
e.printStackTrace();
}
return str;
}
文件解密
因為數(shù)據(jù)加密后的長度與明文是不一致的,而文件加密只是部分加密,所以需要記錄下密文的長度,從而讀取密文,完成解密
public static void decryptFile(File file, SecretKey secretKey, int decryptLength) {
try {
// 以 byte 的形式讀取,不改變文件數(shù)據(jù)的編碼格式
byte[] bytes = Files.readAllBytes(file.toPath());
// 截取密文數(shù)據(jù)
byte[] substring = new byte[decryptLength];
System.arraycopy(bytes, 0, substring, 0, decryptLength);
// 解密
byte[] decrypt = decrypt(substring, secretKey);
// 使用明文替換加密數(shù)據(jù)
byte[] newContent = new byte[decrypt.length + bytes.length - decryptLength];
System.arraycopy(decrypt, 0, newContent, 0, decrypt.length);
System.arraycopy(bytes, decryptLength, newContent, decrypt.length, bytes.length - decryptLength);
// 覆蓋寫入文件
Files.write(file.toPath(), newContent);
} catch (Exception e) {
e.printStackTrace();
}
}
解密
private static byte[] decrypt(byte[] bytes, SecretKey secretKey) {
byte[] decryptStr = null;
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
decryptStr = cipher.doFinal(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return decryptStr;
}
源碼
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
/**
* AES 加解密
*/
public class AESEncrypt {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
/**
* 生成 SecretKey
* @param secret
* @param salt
* @return
*/
public static SecretKey generateSecretKey(String secret, String salt) {
SecretKey secretKey = null;
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec keySpec = new PBEKeySpec(secret.toCharArray(), salt.getBytes(), 65536, 256);
secretKey = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), "AES");
} catch (Exception e) {
e.printStackTrace();
}
return secretKey;
}
/**
* 加密
* @param content
* @param secretKey
* @return
*/
private static byte[] encrypt(byte[] content, SecretKey secretKey) {
byte[] str = null;
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
str = cipher.doFinal(content);
} catch (Exception e) {
e.printStackTrace();
}
return str;
}
/**
* 解密
* @param bytes
* @param secretKey
* @return
*/
private static byte[] decrypt(byte[] bytes, SecretKey secretKey) {
byte[] decryptStr = null;
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
decryptStr = cipher.doFinal(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return decryptStr;
}
/**
* 文件加密
* @param file
* @param secretKey
*/
public static int encryptFile(File file, SecretKey secretKey, int encryptLength) {
try {
// 以 byte 的形式讀取,不改變文件數(shù)據(jù)的編碼格式
byte[] bytes = Files.readAllBytes(file.toPath());
// 僅加密 encryptLength 長度的數(shù)據(jù)
byte[] substring = new byte[encryptLength];
System.arraycopy(bytes, 0, substring, 0, encryptLength);
// 加密
byte[] encrypt = encrypt(substring, secretKey);
// 使用密文替換老數(shù)據(jù)
byte[] newContent = new byte[encrypt.length + bytes.length - encryptLength];
System.arraycopy(encrypt, 0, newContent, 0, encrypt.length);
System.arraycopy(bytes, encryptLength, newContent, encrypt.length, bytes.length - encryptLength);
// 覆蓋寫入文件
Files.write(file.toPath(), newContent);
return encrypt.length;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* 文件解密
* @param file
* @param secretKey
* @param decryptLength
*/
public static void decryptFile(File file, SecretKey secretKey, int decryptLength) {
try {
// 以 byte 的形式讀取,不改變文件數(shù)據(jù)的編碼格式
byte[] bytes = Files.readAllBytes(file.toPath());
// 截取密文數(shù)據(jù)
byte[] substring = new byte[decryptLength];
System.arraycopy(bytes, 0, substring, 0, decryptLength);
// 解密
byte[] decrypt = decrypt(substring, secretKey);
// 使用明文替換加密數(shù)據(jù)
byte[] newContent = new byte[decrypt.length + bytes.length - decryptLength];
System.arraycopy(decrypt, 0, newContent, 0, decrypt.length);
System.arraycopy(bytes, decryptLength, newContent, decrypt.length, bytes.length - decryptLength);
// 覆蓋寫入文件
Files.write(file.toPath(), newContent);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
// generate secret key
SecretKey secretKey = generateSecretKey("password", "salt");
File file = new File(args[0]);
long encryptStart = System.currentTimeMillis();
int encryptLength = encryptFile(file, secretKey, 128);
long encryptEnd = System.currentTimeMillis();
System.out.printf("Encrypt %s cost %d%n", args[0], (encryptEnd - encryptStart));
decryptFile(file, secretKey, encryptLength);
System.out.printf("Decrypt %s cost %d%n", args[0], (System.currentTimeMillis() - encryptEnd));
} catch (Exception e) {
e.printStackTrace();
}
}
}
