Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save lry127/589bbd8ead1258827e6f81413c514df6 to your computer and use it in GitHub Desktop.
Save lry127/589bbd8ead1258827e6f81413c514df6 to your computer and use it in GitHub Desktop.
AESGCMCryptoWrapper.java
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