Java AES加密和解密
1.概述
對稱密鑰塊密碼在數據加密中起重要作用。這意味著同一密鑰可用於加密和解密。高級加密標準(AES)是一種廣泛使用的對稱密鑰加密算法。
在本教程中,我們將看到如何使用JDK中的Java密碼體系結構(JCA)來實現AES加密和解密。
2. AES算法
AES算法是一種迭代的對稱密鑰塊密碼,它支持128、192和256位的加密密鑰(秘密密鑰),以對128位的塊中的數據進行加密和解密。下圖顯示了高級AES算法:
如果要加密的數據不滿足128位的塊大小要求,則必須對其進行填充。填充是將最後一塊填充為128位的過程。
3. AES變體
AES算法具有六種操作模式:
- ECB(電子密碼本)
- CBC(密碼塊鏈接)
- CFB(密碼反饋)
- OFB(輸出反饋)
- 點擊率(計數器)
- GCM(Galois /計數器模式)
可以應用操作模式以增強加密算法的效果。此外,操作模式可以將分組密碼轉換為流密碼。每種模式都有其優勢和劣勢。讓我們快速回顧一下。
3.1。ECB
這種操作模式是最簡單的。明文分為大小為128位的塊。然後,將使用相同的密鑰和算法對每個塊進行加密。因此,對於相同的塊它會產生相同的結果。這是此模式的主要缺點,不建議用於加密。它需要填充數據。
3.2。CBC
為了克服ECB的弱點,CBC模式使用初始化向量(IV)來增強加密。首先,CBC將明文塊xor與IV一起使用。然後,它將結果加密到密文塊。在下一個塊中,它將使用加密結果與明文塊進行異或,直到最後一個塊。
在這種模式下,加密不能並行化,但解密可以並行化。它還需要填充數據。
3.3。 CFB
此模式可用作流密碼。首先,它對IV進行加密,然後將其與純文本塊進行異或運算以獲得密文。然後,CFB將加密結果加密以對明文進行異或。它需要IV。
在這種模式下,解密可以並行化,但加密不能並行化。
3.4。OFB
此模式可用作流密碼。首先,它加密IV。然後,它使用加密結果對明文進行異或運算以獲得密文。
它不需要填充數據,也不會受到噪聲塊的影響。
3.5。CTR
該模式將計數器的值用作IV。它與OFB非常相似,但是它每次使用計數器而不是IV對其進行加密。
此模式具有兩個優點,包括加密/解密並行化,並且一個塊中的噪聲不會影響其他塊。
3.6。 GCM
此模式是CTR模式的擴展。 GCM已受到NIST的高度重視並推薦使用。 GCM模型輸出密文和認證標籤。與該算法的其他運算模式相比,此模式的主要優點是效率高。
在本教程中,我們將使用AES/CBC/PKCS5Padding
算法,因為該算法已在許多項目中廣泛使用。
3.7。加密後的數據大小
如前所述,AES的塊大小為128位或16個字節。 AES不會更改大小,並且密文大小等於明文大小。另外,在ECB和CBC模式下,我們應該使用類似於PKCS 5.
的填充算法PKCS 5.
因此,加密後的數據大小為:
ciphertext_size (bytes) = cleartext_size + (16 - (cleartext_size % 16))
為了用密文存儲IV,我們需要再增加16個字節。
4. AES參數
在AES算法中,我們需要三個參數:輸入數據,秘密密鑰和IV。 ECB模式下不使用IV。
4.1。輸入數據
AES的輸入數據可以是基於字符串,文件,對象和密碼的。
4.2。密鑰
在AES中生成密鑰的方法有兩種:從隨機數生成或從給定密碼生成。
在第一種方法中,應該從像SecureRandom
類這樣的加密安全(偽)隨機數生成器生成秘密密鑰。
為了生成密鑰,我們可以使用KeyGenerator
類。讓我們定義一種用於生成大小為n
(128、192和256)位的AES密鑰的方法:
public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(n);
SecretKey key = keyGenerator.generateKey();
return key;
}
在第二種方法中,可以使用基於密碼的密鑰派生功能(例如PBKDF2)從給定的密碼派生AES秘密密鑰。我們還需要一個鹽值來將密碼轉換為密鑰。鹽也是一個隨機值。
我們可以將SecretKeyFactory
類與PBKDF2WithHmacSHA256
算法一起使用,以根據給定的密碼生成密鑰。
讓我們定義一種方法,該方法可通過65,536次迭代和256位密鑰長度從給定密碼生成AES密鑰:
public static SecretKey getKeyFromPassword(String password, String salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return secret;
}
4.3。初始化向量(IV)
IV是偽隨機值,其大小與加密的塊相同。我們可以使用SecureRandom
類生成隨機IV。
讓我們定義一種生成IV的方法:
public static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
5.加密和解密
5.1。串
要實現輸入字符串加密,我們首先需要根據上一節生成密鑰和IV。下一步,我們使用getInstance()
方法從Cipher
類創建一個實例。
此外,我們使用帶有秘密密鑰,IV和加密模式的init()
方法配置密碼實例。最後,我們通過調用doFinal()
方法對輸入字符串進行加密。此方法獲取輸入字節並以字節為單位返回密文:
public static String encrypt(String algorithm, String input, SecretKey key,
IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] cipherText = cipher.doFinal(input.getBytes());
return Base64.getEncoder()
.encodeToString(cipherText);
}
為了解密輸入字符串,我們可以使用DECRYPT_MODE
初始化密碼來解密內容:
public static String decrypt(String algorithm, String cipherText, SecretKey key,
IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] plainText = cipher.doFinal(Base64.getDecoder()
.decode(cipherText));
return new String(plainText);
}
讓我們編寫一個用於加密和解密字符串輸入的測試方法:
@Test
void givenString_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchPaddingException {
String input = "baeldung";
SecretKey key = AESUtil.generateKey(128);
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
String algorithm = "AES/CBC/PKCS5Padding";
String cipherText = AESUtil.encrypt(algorithm, input, key, ivParameterSpec);
String plainText = AESUtil.decrypt(algorithm, cipherText, key, ivParameterSpec);
Assertions.assertEquals(input, plainText);
}
5.2。文件
現在,讓我們使用AES算法加密文件。步驟是相同的,但是我們需要一些IO
類來處理文件。讓我們加密一個文本文件:
public static void encryptFile(String algorithm, SecretKey key, IvParameterSpec iv,
File inputFile, File outputFile) throws IOException, NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
FileInputStream inputStream = new FileInputStream(inputFile);
FileOutputStream outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[64];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
byte[] output = cipher.update(buffer, 0, bytesRead);
if (output != null) {
outputStream.write(output);
}
}
byte[] outputBytes = cipher.doFinal();
if (outputBytes != null) {
outputStream.write(outputBytes);
}
inputStream.close();
outputStream.close();
}
請注意,不建議嘗試將整個文件(尤其是大文件)讀入內存。相反,我們一次加密一個緩衝區。
為了解密文件,我們使用類似的步驟,並使用DECRYPT_MODE
初始化密碼, DECRYPT_MODE
。
再次,讓我們定義一個用於加密和解密文本文件的測試方法。在這種方法中,我們從測試資源目錄中讀取baeldung.txt
文件,將其加密為一個名為baeldung.encrypted
的文件,然後將該文件解密為一個新文件:
@Test
void givenFile_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IOException, IllegalBlockSizeException,
InvalidKeyException, BadPaddingException, InvalidAlgorithmParameterException,
NoSuchPaddingException {
SecretKey key = AESUtil.generateKey(128);
String algorithm = "AES/CBC/PKCS5Padding";
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
Resource resource = new ClassPathResource("inputFile/baeldung.txt");
File inputFile = resource.getFile();
File encryptedFile = new File("classpath:baeldung.encrypted");
File decryptedFile = new File("document.decrypted");
AESUtil.encryptFile(algorithm, key, ivParameterSpec, inputFile, encryptedFile);
AESUtil.decryptFile(
algorithm, key, ivParameterSpec, encryptedFile, decryptedFile);
assertThat(inputFile).hasSameTextualContentAs(decryptedFile);
}
5.3。基於密碼
我們可以使用從給定密碼派生的密鑰進行AES加密和解密。
為了生成密鑰,我們使用getKeyFromPassword()
方法。加密和解密步驟與字符串輸入部分中顯示的步驟相同。然後,我們可以使用實例化的密碼和提供的密鑰來執行加密。
讓我們寫一個測試方法:
@Test
void givenPassword_whenEncrypt_thenSuccess()
throws InvalidKeySpecException, NoSuchAlgorithmException,
IllegalBlockSizeException, InvalidKeyException, BadPaddingException,
InvalidAlgorithmParameterException, NoSuchPaddingException {
String plainText = "www.baeldung.com";
String password = "baeldung";
String salt = "12345678";
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
SecretKey key = AESUtil.getKeyFromPassword(password,salt);
String cipherText = AESUtil.encryptPasswordBased(plainText, key, ivParameterSpec);
String decryptedCipherText = AESUtil.decryptPasswordBased(
cipherText, key, ivParameterSpec);
Assertions.assertEquals(plainText, decryptedCipherText);
}
5.4。目的
為了加密Java對象,我們需要使用SealedObject
類。該對象應可Serializable
。讓我們從定義Student
類開始:
public class Student implements Serializable {
private String name;
private int age;
// standard setters and getters
}
接下來,讓我們加密Student
對象:
public static SealedObject encryptObject(String algorithm, Serializable object,
SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException,
InvalidKeyException, IOException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
SealedObject sealedObject = new SealedObject(object, cipher);
return sealedObject;
}
稍後可以使用正確的密碼對加密對象進行解密:
public static Serializable decryptObject(String algorithm, SealedObject sealedObject,
SecretKey key, IvParameterSpec iv) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException,
ClassNotFoundException, BadPaddingException, IllegalBlockSizeException,
IOException {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
Serializable unsealObject = (Serializable) sealedObject.getObject(cipher);
return unsealObject;
}
讓我們寫一個測試用例:
@Test
void givenObject_whenEncrypt_thenSuccess()
throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException,
InvalidAlgorithmParameterException, NoSuchPaddingException, IOException,
BadPaddingException, ClassNotFoundException {
Student student = new Student("Baeldung", 20);
SecretKey key = AESUtil.generateKey(128);
IvParameterSpec ivParameterSpec = AESUtil.generateIv();
String algorithm = "AES/CBC/PKCS5Padding";
SealedObject sealedObject = AESUtil.encryptObject(
algorithm, student, key, ivParameterSpec);
Student object = (Student) AESUtil.decryptObject(
algorithm, sealedObject, key, ivParameterSpec);
assertThat(student).isEqualToComparingFieldByField(object);
}
六,結論
總之,我們已經學習瞭如何使用Java中的AES算法來加密和解密輸入數據,例如字符串,文件,對象和基於密碼的數據。此外,我們討論了AES的變化以及加密後數據的大小。