Java加密与解密的艺术~AES-GCM-NoPadding实现
來源:Java AES加密和解密_一名可愛的技術搬運工-CSDN博客
高級加密標準?(AES,Rijndael)是一種分組密碼加密和解密算法,是全球使用最廣泛的加密算法。 AES使用128、192或256位的密鑰來處理128位的塊。
本文向您展示了一些Java?AES加密和解密示例:
- AES字符串加密–(加密和解密字符串)。
- AES基于密碼的加密–(密鑰將從給定的密碼派生)。
- AES文件加密。 (基于密碼)。
在本文中,我們重點介紹通過Galois Counter Mode(GCM)進行的256位AES加密。
GCM = CTR + Authentication.進一步閱讀?
閱讀本–?NIST – Galois /計數器模式(GCM)的建議
不要使用AES電子密碼本(ECB)模式?
AES?ECB模式或AES/ECB/PKCS5Padding?(在Java中)在語義上并不安全?– ECB加密的密文可能泄漏有關純文本的信息。 這是關于為什么不應該使用ECB加密的討論。
1. Java和AES加密輸入。
在AES加密和解密中,我們需要以下輸入:
AES加密最佳做法?
不要重復使用具有相同密鑰的IV。
1.1 IV(初始值或初始向量),它是隨機字節,通常為12個字節或16個字節。 在Java中,我們可以使用SecureRandom生成隨機IV。
// 16 bytes IVpublic static byte[] getRandomNonce() {byte[] nonce = new byte[16];new SecureRandom().nextBytes(nonce);return nonce;}// 12 bytes IVpublic static byte[] getRandomNonce() {byte[] nonce = new byte[12];new SecureRandom().nextBytes(nonce);return nonce;}1.2 AES密鑰,即AES-128或AES-256?。 在Java中,我們可以使用KeyGenerator生成AES密鑰。
// 256 bits AES secret keypublic static SecretKey getAESKey() throws NoSuchAlgorithmException {KeyGenerator keyGen = KeyGenerator.getInstance("AES");keyGen.init(256, SecureRandom.getInstanceStrong());return keyGen.generateKey();}1.3從給定密碼派生的AES密鑰。 在Java中,我們可以使用SecretKeyFactory和PBKDF2WithHmacSHA256從給定的密碼生成AES密鑰。
// AES key derived from a passwordpublic static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)throws NoSuchAlgorithmException, InvalidKeySpecException {SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");// iterationCount = 65536// keyLength = 256KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");return secret;}我們使用salt來保護彩虹攻擊,它也是一個隨機字節,我們可以使用相同的1.1 getRandomNonce生成它。
1.4我們將上述方法分組為一個util類,這樣我們就不會一次又一次重復相同的代碼。
CryptoUtils.java
package com.mkyong.crypto.utils;import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.ArrayList; import java.util.List;public class CryptoUtils {public static byte[] getRandomNonce(int numBytes) {byte[] nonce = new byte[numBytes];new SecureRandom().nextBytes(nonce);return nonce;}// AES secret keypublic static SecretKey getAESKey(int keysize) throws NoSuchAlgorithmException {KeyGenerator keyGen = KeyGenerator.getInstance("AES");keyGen.init(keysize, SecureRandom.getInstanceStrong());return keyGen.generateKey();}// Password derived AES 256 bits secret keypublic static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)throws NoSuchAlgorithmException, InvalidKeySpecException {SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");// iterationCount = 65536// keyLength = 256KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");return secret;}// hex representationpublic static String hex(byte[] bytes) {StringBuilder result = new StringBuilder();for (byte b : bytes) {result.append(String.format("%02x", b));}return result.toString();}// print hex with block size splitpublic static String hexWithBlockSize(byte[] bytes, int blockSize) {String hex = hex(bytes);// one hex = 2 charsblockSize = blockSize * 2;// better idea how to print this?List<String> result = new ArrayList<>();int index = 0;while (index < hex.length()) {result.add(hex.substring(index, Math.min(index + blockSize, hex.length())));index += blockSize;}return result.toString();}}package com.mkyong.crypto.utils;
2. AES加密和解密。
AES-GSM是使用最廣泛的認證密碼。 本示例將在Galois計數器模式(GCM)中使用256位AES加密和解密字符串。
AES-GCM輸入:
- AES密鑰(256位)
- IV – 96位(12字節)
- 身份驗證標簽的長度(以位為單位)– 128位(16字節)
2.1在Java中,我們使用AES/GCM/NoPadding表示AES-GCM算法。 對于加密的輸出,我們將16字節的IV前綴到加密的文本(密文)之前,因為解密需要相同的IV。
如果IV是眾所周知的,可以嗎??
IV公開是可以的,唯一的秘訣就是密鑰,對它保密并保密。
本示例將使用AES加密純文本Hello World AES-GCM?,然后將其解密回原始純文本。
EncryptorAesGcm.java
package com.mkyong.crypto.encryptor;import com.mkyong.crypto.utils.CryptoUtils;import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets;/*** AES-GCM inputs - 12 bytes IV, need the same IV and secret keys for encryption and decryption.* <p>* The output consist of iv, encrypted content, and auth tag in the following format:* output = byte[] {i i i c c c c c c ...}* <p>* i = IV bytes* c = content bytes (encrypted content, auth tag)*/ public class EncryptorAesGcm {private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";private static final int TAG_LENGTH_BIT = 128;private static final int IV_LENGTH_BYTE = 12;private static final int AES_KEY_BIT = 256;private static final Charset UTF_8 = StandardCharsets.UTF_8;// AES-GCM needs GCMParameterSpecpublic static byte[] encrypt(byte[] pText, SecretKey secret, byte[] iv) throws Exception {Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));byte[] encryptedText = cipher.doFinal(pText);return encryptedText;}// prefix IV length + IV bytes to cipher textpublic static byte[] encryptWithPrefixIV(byte[] pText, SecretKey secret, byte[] iv) throws Exception {byte[] cipherText = encrypt(pText, secret, iv);byte[] cipherTextWithIv = ByteBuffer.allocate(iv.length + cipherText.length).put(iv).put(cipherText).array();return cipherTextWithIv;}public static String decrypt(byte[] cText, SecretKey secret, byte[] iv) throws Exception {Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));byte[] plainText = cipher.doFinal(cText);return new String(plainText, UTF_8);}public static String decryptWithPrefixIV(byte[] cText, SecretKey secret) throws Exception {ByteBuffer bb = ByteBuffer.wrap(cText);byte[] iv = new byte[IV_LENGTH_BYTE];bb.get(iv);//bb.get(iv, 0, iv.length);byte[] cipherText = new byte[bb.remaining()];bb.get(cipherText);String plainText = decrypt(cipherText, secret, iv);return plainText;}public static void main(String[] args) throws Exception {String OUTPUT_FORMAT = "%-30s:%s";String pText = "Hello World AES-GCM, Welcome to Cryptography!";// encrypt and decrypt need the same key.// get AES 256 bits (32 bytes) keySecretKey secretKey = CryptoUtils.getAESKey(AES_KEY_BIT);// encrypt and decrypt need the same IV.// AES-GCM needs IV 96-bit (12 bytes)byte[] iv = CryptoUtils.getRandomNonce(IV_LENGTH_BYTE);byte[] encryptedText = EncryptorAesGcm.encryptWithPrefixIV(pText.getBytes(UTF_8), secretKey, iv);System.out.println("\n------ AES GCM Encryption ------");System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText));System.out.println(String.format(OUTPUT_FORMAT, "Key (hex)", CryptoUtils.hex(secretKey.getEncoded())));System.out.println(String.format(OUTPUT_FORMAT, "IV (hex)", CryptoUtils.hex(iv)));System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (hex) ", CryptoUtils.hex(encryptedText)));System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (hex) (block = 16)", CryptoUtils.hexWithBlockSize(encryptedText, 16)));System.out.println("\n------ AES GCM Decryption ------");System.out.println(String.format(OUTPUT_FORMAT, "Input (hex)", CryptoUtils.hex(encryptedText)));System.out.println(String.format(OUTPUT_FORMAT, "Input (hex) (block = 16)", CryptoUtils.hexWithBlockSize(encryptedText, 16)));System.out.println(String.format(OUTPUT_FORMAT, "Key (hex)", CryptoUtils.hex(secretKey.getEncoded())));String decryptedText = EncryptorAesGcm.decryptWithPrefixIV(encryptedText, secretKey);System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));}}package com.mkyong.crypto.encryptor;
輸出量
純文本:?Hello World AES-GCM
Terminal
------ AES GCM Encryption ------ Input (plain text) :Hello World AES-GCM Key (hex) :603d87185bf855532f14a77a91ec7b025c004bf664e9f5c6e95613ee9577f436 IV (hex) :bdb271ce5235996a0709e09c Encrypted (hex) :bdb271ce5235996a0709e09c2d03eefe319e9329768724755c56291aecaef88cd1e6bdf72b8c7b54d75a94e66b0cd3 Encrypted (hex) (block = 16) :[bdb271ce5235996a0709e09c2d03eefe, 319e9329768724755c56291aecaef88c, d1e6bdf72b8c7b54d75a94e66b0cd3]------ AES GCM Decryption ------ Input (hex) :bdb271ce5235996a0709e09c2d03eefe319e9329768724755c56291aecaef88cd1e6bdf72b8c7b54d75a94e66b0cd3 Input (hex) (block = 16) :[bdb271ce5235996a0709e09c2d03eefe, 319e9329768724755c56291aecaef88c, d1e6bdf72b8c7b54d75a94e66b0cd3] Key (hex) :603d87185bf855532f14a77a91ec7b025c004bf664e9f5c6e95613ee9577f436 Decrypted (plain text) :Hello World AES-GCM------ AES GCM Encryption ------
純文本:?Hello World AES-GCM, Welcome to Cryptography!
Terminal
------ AES GCM Encryption ------ Input (plain text) :Hello World AES-GCM, Welcome to Cryptography! Key (hex) :ddc24663d104e1c2f81f11aef98156503dafdc435f81e3ac3d705015ebab095c IV (hex) :b05d6aedf023f73b9e1e2d11 Encrypted (hex) :b05d6aedf023f73b9e1e2d11f6f5137d971aea8c5cdd5b045e0960eb4408e0ee4635cccc2dfeec2c13a89bd400f659be82dc2329e9c36e3b032f38bd42296a8495ac840b0625c097d9 Encrypted (hex) (block = 16) :[b05d6aedf023f73b9e1e2d11f6f5137d, 971aea8c5cdd5b045e0960eb4408e0ee, 4635cccc2dfeec2c13a89bd400f659be, 82dc2329e9c36e3b032f38bd42296a84, 95ac840b0625c097d9]------ AES GCM Decryption ------ Input (hex) :b05d6aedf023f73b9e1e2d11f6f5137d971aea8c5cdd5b045e0960eb4408e0ee4635cccc2dfeec2c13a89bd400f659be82dc2329e9c36e3b032f38bd42296a8495ac840b0625c097d9 Input (hex) (block = 16) :[b05d6aedf023f73b9e1e2d11f6f5137d, 971aea8c5cdd5b045e0960eb4408e0ee, 4635cccc2dfeec2c13a89bd400f659be, 82dc2329e9c36e3b032f38bd42296a84, 95ac840b0625c097d9] Key (hex) :ddc24663d104e1c2f81f11aef98156503dafdc435f81e3ac3d705015ebab095c Decrypted (plain text) :Hello World AES-GCM, Welcome to Cryptography!------ AES GCM Encryption ------
3.基于AES密碼的加密和解密。
對于基于密碼的加密,我們可以使用定義為RFC 8018的基于密碼的密碼規范(PKCS)從給定的密碼生成密鑰。
對于PKCS輸入:
- 密碼,您提供。
- 鹽–至少64位(8字節)隨機字節。
- 迭代計數–建議最小迭代計數為1,000。
什么是鹽和迭代計數?
- salt會為給定的密碼生成廣泛的密鑰集。 例如,如果鹽是128位,則每個密碼將有多達2 ^ 128個密鑰。 因此,它增加了彩虹攻擊的難度。 此外,攻擊者為一個用戶的密碼構建的彩虹表對于另一用戶變得毫無用處。
- iteration count增加了從密碼生成密鑰的成本,因此增加了難度并減慢了攻擊速度。
3.1對于加密的輸出,我們在密文前面加上12 bytes IV和password salt?,因為我們需要相同的IV和密碼鹽(用于密鑰)進行解密。 此外,我們使用Base64編碼器將加密的文本編碼為字符串表示形式,以便我們可以以字符串格式(字節數組)發送加密的文本或密文。
如果密碼鹽是眾所周知的,可以嗎??
與IV相同,并且可以公開知道密碼鹽,唯一的秘訣就是密鑰,并對其進行保密和保密。
EncryptorAesGcmPassword.java
package com.mkyong.crypto.encryptor;import com.mkyong.crypto.utils.CryptoUtils;import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64;/*** AES-GCM inputs - 12 bytes IV, need the same IV and secret keys for encryption and decryption.* <p>* The output consist of iv, password's salt, encrypted content and auth tag in the following format:* output = byte[] {i i i s s s c c c c c c ...}* <p>* i = IV bytes* s = Salt bytes* c = content bytes (encrypted content)*/ public class EncryptorAesGcmPassword {private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";private static final int TAG_LENGTH_BIT = 128; // must be one of {128, 120, 112, 104, 96}private static final int IV_LENGTH_BYTE = 12;private static final int SALT_LENGTH_BYTE = 16;private static final Charset UTF_8 = StandardCharsets.UTF_8;// return a base64 encoded AES encrypted textpublic static String encrypt(byte[] pText, String password) throws Exception {// 16 bytes saltbyte[] salt = CryptoUtils.getRandomNonce(SALT_LENGTH_BYTE);// GCM recommended 12 bytes iv?byte[] iv = CryptoUtils.getRandomNonce(IV_LENGTH_BYTE);// secret key from passwordSecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt);Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);// ASE-GCM needs GCMParameterSpeccipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));byte[] cipherText = cipher.doFinal(pText);// prefix IV and Salt to cipher textbyte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length).put(iv).put(salt).put(cipherText).array();// string representation, base64, send this string to other for decryption.return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);}// we need the same password, salt and iv to decrypt itprivate static String decrypt(String cText, String password) throws Exception {byte[] decode = Base64.getDecoder().decode(cText.getBytes(UTF_8));// get back the iv and salt from the cipher textByteBuffer bb = ByteBuffer.wrap(decode);byte[] iv = new byte[IV_LENGTH_BYTE];bb.get(iv);byte[] salt = new byte[SALT_LENGTH_BYTE];bb.get(salt);byte[] cipherText = new byte[bb.remaining()];bb.get(cipherText);// get back the aes key from the same password and saltSecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt);Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));byte[] plainText = cipher.doFinal(cipherText);return new String(plainText, UTF_8);}public static void main(String[] args) throws Exception {String OUTPUT_FORMAT = "%-30s:%s";String PASSWORD = "this is a password";String pText = "AES-GSM Password-Bases encryption!";String encryptedTextBase64 = EncryptorAesGcmPassword.encrypt(pText.getBytes(UTF_8), PASSWORD);System.out.println("\n------ AES GCM Password-based Encryption ------");System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText));System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (base64) ", encryptedTextBase64));System.out.println("\n------ AES GCM Password-based Decryption ------");System.out.println(String.format(OUTPUT_FORMAT, "Input (base64)", encryptedTextBase64));String decryptedText = EncryptorAesGcmPassword.decrypt(encryptedTextBase64, PASSWORD);System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));}}package com.mkyong.crypto.encryptor;
輸出量
Terminal
------ AES GCM Password-based Encryption ------ Input (plain text) :AES-GSM Password-Bases encryption! Encrypted (base64) :KmrvjnMusJTQo/hB7T5BvlQpvi3bVbdjpZP51NT7I/enrIfSQuDfSK6iXgdPzvUP2IE54mwrKiyHqMkG8224lRZ9tXHcclmdh98I8b3B------ AES GCM Password-based Decryption ------ Input (base64) :KmrvjnMusJTQo/hB7T5BvlQpvi3bVbdjpZP51NT7I/enrIfSQuDfSK6iXgdPzvUP2IE54mwrKiyHqMkG8224lRZ9tXHcclmdh98I8b3B Decrypted (plain text) :AES-GSM Password-Bases encryption!------ AES GCM Password-based Encryption ------
3.2如果密碼不匹配,Java會拋出AEADBadTagException: Tag mismatch!
// change the password to something elseString decryptedText = EncryptorAesGcmPassword.decrypt(encryptedTextBase64, "other password");System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));// change the password to something else
輸出量
Terminal
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623)at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1118)at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1055)at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:855)at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2207)at com.mkyong.crypto.encryptor.EncryptorAesGcmPassword.decrypt(EncryptorAesGcmPassword.java:88)at com.mkyong.crypto.encryptor.EncryptorAesGcmPassword.main(EncryptorAesGcmPassword.java:109)Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
4. AES文件加密和解密。
此示例是基于AES密碼的文件加密。 想法是相同的,但是我們需要一些IO類來處理資源或文件。
這是resources文件夾中的文本文件。
readme.txt
This is line 1. This is line 2. This is line 3. This is line 4. This is line 5. This is line 9. This is line 10.This is line 1.
4.1此示例類似于3.1 EncryptorAesGcmPassword.java?,但有一些小的更改,例如返回byte[]而不是base64編碼的字符串。
public static byte[] encrypt(byte[] pText, String password) throws Exception {//...// prefix IV and Salt to cipher textbyte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length).put(iv).put(salt).put(cipherText).array();// it works, even if we save the based64 encoded string into a file.// return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);// we save the byte[] into a file.return cipherTextWithIvSalt;}public static byte[] encrypt(byte[] pText, String password) throws Exception {
添加encryptFile和decryptFile工作與文件。
public static void encryptFile(String fromFile, String toFile, String password) throws Exception {// read a normal txt filebyte[] fileContent = Files.readAllBytes(Paths.get(ClassLoader.getSystemResource(fromFile).toURI()));// encrypt with a passwordbyte[] encryptedText = EncryptorAesGcmPasswordFile.encrypt(fileContent, password);// save a filePath path = Paths.get(toFile);Files.write(path, encryptedText);}public static byte[] decryptFile(String fromEncryptedFile, String password) throws Exception {// read a filebyte[] fileContent = Files.readAllBytes(Paths.get(fromEncryptedFile));return EncryptorAesGcmPasswordFile.decrypt(fileContent, password);}public static void encryptFile(
4.2從類路徑中讀取以上readme.txt文件,對其進行加密,然后將加密的數據保存到新文件c:\test\readme.encrypted.txt?。
String password = "password123";String fromFile = "readme.txt"; // from resources folderString toFile = "c:\\test\\readme.encrypted.txt";// encrypt fileEncryptorAesGcmPasswordFile.encryptFile(fromFile, toFile, password);String password = "password123";
輸出量
4.3讀取加密的文件,解密并打印輸出。
String password = "password123";String toFile = "c:\\test\\readme.encrypted.txt";// decrypt filebyte[] decryptedText = EncryptorAesGcmPasswordFile.decryptFile(toFile, password);String pText = new String(decryptedText, UTF_8);System.out.println(pText);String password = "password123";
輸出量
Terminal
This is line 1. This is line 2. This is line 3. This is line 4. This is line 5. This is line 9. This is line 10.This is line 1.
PS AES圖像加密是相同的概念。
下載源代碼
$ git clone?https://github.com/mkyong/core-java
$ cd java-crypto
讓我知道文章是否需要改進。 謝謝。
參考文獻
- 維基百科–密碼JavaDoc
- 維基百科–密碼塊鏈接(CBC)
- 維基百科-Galois / Counter Mode(GCM)
- Oracle – KeyGenerator算法JavaDoc
- Java –如何生成隨機的12個字節?
- 為什么不應該使用ECB加密?
- Spring Security加密模塊
- 維基百科– PBKDF2
- RFC 8018 – PKCS
- Java –如何連接和分割字節數組
- Java安全標準算法名稱
- NIST – Galois /計數器模式(GCM)的建議
翻譯自:?Java AES encryption and decryption - Mkyong.com?
總結
以上是生活随笔為你收集整理的Java加密与解密的艺术~AES-GCM-NoPadding实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: teamviewer企业版 添加计算机,
- 下一篇: 无法初始化链接服务器 (null) 的