Published Function - aes256GCMDecryptBytes(bytesToDecrypt, password)
function aes256GCMDecryptBytes(bytesToDecrypt, password) {
//Wrap the bytesToDecrypt into a byte buffer to ease the reading process
var byteBuffer = java.nio.ByteBuffer.wrap(bytesToDecrypt);
//First get the size of the nonce from the beginning of the bytesToDecrypt
var nonceSize = byteBuffer.getInt();
//Make sure that the bytesToDecrypt were encrypted properly
if (nonceSize < 12 || nonceSize >= 16) {
throw new java.lang.IllegalArgumentException("Nonce size is incorrect. Make sure that the bytesToDecrypt were properly encrypted.");
}
//Next get the nonce from the bytesToDecrypt
var iv = java.nio.ByteBuffer.allocate(nonceSize).array();
byteBuffer.get(iv);
//Generate a secret key
var secretKey = generateSecretKey(password, iv);
//Now get the actual encrypted data from bytesToDecrypt
var encryptedBytes = java.nio.ByteBuffer.allocate(byteBuffer.remaining()).array();
byteBuffer.get(encryptedBytes);
// Get Cipher Instance using AES with the GCM mode
var cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding");
// Create GCMParameterSpec
var gcmParameterSpec = new javax.crypto.spec.GCMParameterSpec(16 * 8, iv); //Tag length should be 16 (GCM_TAG_LENGTH)
// Initialize Cipher for DECRYPT_MODE
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
// Perform Decryption
var decryptedText = cipher.doFinal(encryptedBytes);
return new java.lang.String(decryptedText);
}
Published Function - generateSecretKey(password, iv)
function generateSecretKey(password, iv) {
//The iv/nonce is also used as the salt for the hashed password.
var salt = iv;
//Prepare a secret key
var spec = new javax.crypto.spec.PBEKeySpec(new java.lang.String(password).toCharArray(), salt, 65536, 256); //Key size should be 256 (AES_KEY_SIZE)
var secretKeyFactory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
var key = secretKeyFactory.generateSecret(spec).getEncoded();
return new javax.crypto.spec.SecretKeySpec(key, "AES");
}
NOTE: In the generateSecretKey method, this line may be modified to adjust how long it takes to generate a secret key from the password:
var spec = new javax.crypto.spec.PBEKeySpec(new java.lang.String(password).toCharArray(), salt, 65536, 256);
To do so change the third parameter (the iterationCount described here). A lower number takes less time. See the top of this question in which I compared this implementation to an ECB implementation that demonstrates how changing this number affects how long it takes to encrypt and decrypt using 65536 vs 1.
As I prepared this example, here are some of the errors hit and what they mean:
NoSuchPaddingException - This exception is thrown when a particular padding mechanism is requested but is not available in the environment. Example provider could not find it. Could be something wrong with java configuration.
NoSuchAlgorithmException - This exception is thrown when a particular cryptographic algorithm is requested but is not available in the environment. Example: AES/NONE/NoPadding
InvalidKeyException - This is the exception for invalid Keys (invalid encoding, wrong length, uninitialized, etc). Example: "passw" is only 40 bits (5 bytes) and is not valid for AES as key. Key must be 128, 192, or 256 bits. Recommended that the password is used to generate a key, not as the key.
IllegalBlockSizeException - This exception is thrown when the length of data provided to a block cipher is incorrect, i.e., does not match the block size of the cipher. This can happen when decrypting if the encrypted bytes were base64 encoded without base64 decoding before decrypting.
BadPaddingException - Caused during decryption when the bytes being decrypted do not match the encrypted bytes. For example encrypt -> base64 encoded -> decrypt. Forgetting to base64 decode could cause it to error.
See also:
Why is ECB cipher mode not recommended for encryption?
What cipher modes are recommended for encryption?