Created
January 2, 2024 14:56
-
-
Save lry127/589bbd8ead1258827e6f81413c514df6 to your computer and use it in GitHub Desktop.
AESGCMCryptoWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.crypto.*; | |
import javax.crypto.spec.GCMParameterSpec; | |
import javax.crypto.spec.PBEKeySpec; | |
import javax.crypto.spec.SecretKeySpec; | |
import java.nio.charset.StandardCharsets; | |
import java.security.InvalidAlgorithmParameterException; | |
import java.security.InvalidKeyException; | |
import java.security.NoSuchAlgorithmException; | |
import java.security.SecureRandom; | |
import java.security.spec.InvalidKeySpecException; | |
import java.security.spec.KeySpec; | |
import java.util.Arrays; | |
/** | |
* an aes gcm wrapper, use a key, a gcm aad and a salt value to encrypt (decrypt) data and verify message integrity | |
* <br> | |
* this wrapper is thread safe once initialized and can perform encryption (decryption) concurrently | |
* the key, aad and salt is associated with one wrapper while different iv is generated randomly in each encryption | |
* <br> | |
* for detailed usage please see {@link AESGCMCryptoWrapper#main(String...)} | |
*/ | |
public class AESGCMCryptoWrapper { | |
private static final String CIPHER = "AES/GCM/NoPadding"; | |
private static final String KEY_GEN_FACTORY_ALGORITHM = "PBKDF2WithHmacSHA256"; | |
private static final int KEY_LENGTH = 256; | |
private static final int GCM_ADDITIONAL_DATA_LENGTH = 128; | |
private static final int IV_LENGTH = 128; | |
private static final int KEY_GEN_ITER_COUNT = 65536; | |
private final SecretKeySpec secretKeySpec; | |
private final byte[] gcmAAD; | |
private final SecureRandom secureRandom; | |
public AESGCMCryptoWrapper(String key, String gcmAADKey, String salt) { | |
try { | |
secureRandom = new SecureRandom(); | |
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_GEN_FACTORY_ALGORITHM); | |
KeySpec keySpec = new PBEKeySpec(key.toCharArray(), salt.getBytes(StandardCharsets.UTF_8), KEY_GEN_ITER_COUNT, KEY_LENGTH); | |
SecretKey secretKey = factory.generateSecret(keySpec); | |
secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES"); | |
KeySpec gcmADDSpec = new PBEKeySpec(gcmAADKey.toCharArray(), salt.getBytes(StandardCharsets.UTF_8), KEY_GEN_ITER_COUNT, GCM_ADDITIONAL_DATA_LENGTH); | |
SecretKey gcmADDKey = factory.generateSecret(gcmADDSpec); | |
gcmAAD = gcmADDKey.getEncoded(); | |
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) { | |
throw new CryptoException(e); | |
} | |
} | |
public static void main(String... args) { | |
final String key = "I'm the key to wisdom"; | |
final String aadString = "I'm used as additional authentication data"; | |
final String salt = "I'm so salty"; | |
final String text1 = "Some of us get dipped in flat, some in satin, some in gloss. But every once in a while you find someone who’s iridescent, and when you do, nothing will ever compare."; | |
final String text2 = "有些人沦为平庸浅薄,金玉其外,而败絮其中。可不经意间,有一天你会遇到一个彩虹般绚丽的人,从此以后,其他人就不过是匆匆浮云。"; | |
final byte[] bytes1 = text1.getBytes(StandardCharsets.UTF_8); | |
final byte[] bytes2 = text2.getBytes(StandardCharsets.UTF_8); | |
AESGCMCryptoWrapper wrapper = new AESGCMCryptoWrapper(key, aadString, salt); | |
showBytes("original bytes 1 is", bytes1); | |
System.out.println("original text 1 is \"" + text1 + "\""); | |
showBytes("original bytes 2 is", bytes2); | |
System.out.println("original text 2 is \"" + text2 + "\""); | |
System.out.println(); | |
EncryptionResult encryptionResult1 = wrapper.encryptData(bytes1); | |
byte[] customIv = new byte[16]; | |
new SecureRandom().nextBytes(customIv); | |
EncryptionResult encryptionResult2 = wrapper.encryptData(bytes2, customIv); | |
showBytes("encrypted data 1, iv is", encryptionResult1.iv); | |
showBytes("encrypted data 1, encrypted data is", encryptionResult1.encryptedData); | |
System.out.println(); | |
showBytes("encrypted data 2, iv is", encryptionResult2.iv); | |
showBytes("encrypted data 2, encrypted data is", encryptionResult2.encryptedData); | |
System.out.println("generated iv is " + Arrays.toString(customIv) + " obtained iv is " + Arrays.toString(encryptionResult2.iv) + " . does they equals? " + Arrays.equals(customIv, encryptionResult2.iv)); | |
System.out.println(); | |
byte[] decrypted1 = wrapper.decryptData(encryptionResult1.encryptedData, encryptionResult1.iv); | |
String decrypted1Msg = new String(decrypted1, StandardCharsets.UTF_8); | |
byte[] decrypted2 = wrapper.decryptData(encryptionResult2.encryptedData, encryptionResult2.iv); | |
String decrypted2Msg = new String(decrypted2, StandardCharsets.UTF_8); | |
showBytes("decrypted 1 array is", decrypted1); | |
System.out.println("decrypted 1 array equals bytes 1? " + Arrays.equals(decrypted1, bytes1)); | |
System.out.println("decrypted 1 string is \"" + decrypted1Msg + "\" original.equals(decrypted)? " + text1.equals(decrypted1Msg)); | |
System.out.println(); | |
showBytes("decrypted 2 array is", decrypted2); | |
System.out.println("decrypted 2 array equals bytes 2? " + Arrays.equals(decrypted2, bytes2)); | |
System.out.println("decrypted 2 string is \"" + decrypted2Msg + "\" original.equals(decrypted)? " + text2.equals(decrypted2Msg)); | |
System.out.println(); | |
} | |
private static void showBytes(String prompt, byte[] data) { | |
System.out.println(prompt + " " + Arrays.toString(data)); | |
} | |
public EncryptionResult encryptData(byte[] data, byte[] iv) { | |
try { | |
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, iv); | |
byte[] encrypted = cipher.doFinal(data); | |
return new EncryptionResult(cipher.getIV(), encrypted); | |
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | | |
InvalidAlgorithmParameterException | NoSuchPaddingException | NoSuchAlgorithmException e) { | |
throw new CryptoException(e); | |
} | |
} | |
public EncryptionResult encryptData(byte[] data) { | |
return encryptData(data, null); | |
} | |
public EncryptionResult encryptData(byte[] data, byte[] iv, int offset, int len) { | |
try { | |
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, iv); | |
byte[] encrypted = cipher.doFinal(data, offset, len); | |
return new EncryptionResult(cipher.getIV(), encrypted); | |
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | | |
BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException e) { | |
throw new CryptoException(e); | |
} | |
} | |
public EncryptionResult encryptData(byte[] data, int offset, int len) { | |
return encryptData(data, null, offset, len); | |
} | |
public byte[] decryptData(byte[] encrypted, byte[] iv) { | |
if (encrypted == null || iv == null) { | |
throw new CryptoException("neither encrypted data or iv could be null"); | |
} | |
try { | |
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, iv); | |
return cipher.doFinal(encrypted); | |
} catch (Exception e) { | |
throw new CryptoException(e); | |
} | |
} | |
public byte[] decryptData(byte[] encrypted, byte[] iv, int offset, int len) { | |
if (encrypted == null || iv == null) { | |
throw new CryptoException("neither encrypted data or iv could be null"); | |
} | |
try { | |
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, iv); | |
return cipher.doFinal(encrypted, offset, len); | |
} catch (Exception e) { | |
throw new CryptoException(e); | |
} | |
} | |
private GCMParameterSpec getGCMParameterSpec(byte[] iv) { | |
if (iv == null) { | |
iv = new byte[IV_LENGTH / 8]; | |
secureRandom.nextBytes(iv); | |
} | |
return new GCMParameterSpec(GCM_ADDITIONAL_DATA_LENGTH, iv); | |
} | |
private Cipher getCipher(int cipherMode, byte[] iv) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { | |
Cipher cipher = Cipher.getInstance(CIPHER); | |
GCMParameterSpec gcmParameterSpec = getGCMParameterSpec(iv); | |
cipher.init(cipherMode, secretKeySpec, gcmParameterSpec); | |
cipher.updateAAD(gcmAAD); | |
return cipher; | |
} | |
public record EncryptionResult(byte[] iv, byte[] encryptedData) { | |
} | |
public static class CryptoException extends SecurityException { | |
public CryptoException() { | |
super(); | |
} | |
public CryptoException(String reason) { | |
super(reason); | |
} | |
public CryptoException(Throwable cause) { | |
super(cause); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment