javaencryptionhashicorp-vaultaes-gcmsymmetric-key

How can I encrypt data with an already generated AES 256 GCM 96 key (coming from Hashicorp Vault)?


I have a String representing a symmetric key, obtained by using Hashicorp Vault (this may not be important actually). I need this key to encrypt big files, so I cannot send the file directly to Vault asking it to encrypt the data. I want to do it locally instead, so I asked Vault to create a symmetric key for me (by using the transit/datakey/plaintext/ endpoint). I have now a symmetric key (and its ciphertext) that is 44 byte long, generated with aes256_gcm96 algorithm. So my 32 byte key is wrapped with a 96 bits (12 bytes) gcm block, as far I've understood. Now I want to use this key to encrypt my data, but the key is too long to do that, so I need somehow either to unwrap it or call some function that takes in input such a key. I was trying to use Cipher to encrypt my data. This is what I (wrongly) did so far

byte[] datakeyByteArray = mySymmetricKey.getBytes();
SecretKey secretKey = new SecretKeySpec(datakeyByteArray, "AES_256");
Cipher cipher = Cipher.getInstance("AES_256/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);`

When calling the init function, obviously, an exception is thrown: java.security.InvalidKeyException: The key must be 32 bytes

What kind of operation can I do to obtain a valid key?

Thank you.


Solution

  • You already get a link from @Saptarshi Basu that shows in general how to encrypt data with AES GCM. As you see with my code there is nothing really "mystic" doing this but there are some traps to run in.

    Let's start with the most important information - what is the encryption key ? From Hashicorp you received an 44 bytes long string that is the pure 32 bytes long AES GCM key but in Base64-encoding. To get the key usable with Java encryption you need to decode the key to a byte array like this:

    String keyBase64 = "VxJWkOYm2F5z1nF1th9zreS6ZAZMFkCq0c/Ik460ayw=";
    byte[] key = Base64.getDecoder().decode(keyBase64);
    

    The second information we do need is the AES mode - you named it correctly as AES GCM mode and as you provide Java an 32 byte = 256 bit long key it's the requested AES GCM 256 algorithm/mode.

    There is a third parameter necessary for AES GCM encryption and it's the nonce (or sometimes named as initialization vector). Hashicorp tells you to use a 96 bit = 12 byte long nonce. For safety reasons it is important that you use a different nonce each time you encrypt so it is good practice to use a (secure) randomly generated nonce:

    byte[] nonceRandom = new byte[12];
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextBytes(nonceRandom);
    

    Now we are ready for encryption and putting all data together, do the ".doFinal" step and we receive a byte array with the ciphertext. But stop - we need to concatenate the used nonce and the ciphertext to a larger ciphertextWithNonce this way:

    nonce | ciphertext
    

    by simply copying the nonce and ciphertext to a new byte array. This "ciphertextWithNonce" is then Base64 encoded to the final ciphertextBase64 and for upload reasons written to a file.

    If you paste your own key in the beginning of the program and run it you will receive a file named "hashicorp_test.enc" that is ready for upload to your fault.

    This is a sample output (yours will differ as there is a random element):

    Hashicorp Vault AES GCM encryption
    ciphertext: /YB+kfVlIhMowLrsnndD737o2CcyWMfr4xnAADnCBSNCSvMG25aR8UzU2ta8wLwdnHfcago/25KFJ2ky95wpFtsCNE63xRs=
    ciphertext written to file: hashicorp_test.enc
    used key: VxJWkOYm2F5z1nF1th9zreS6ZAZMFkCq0c/Ik460ayw=
    

    If you like to see this code running in an online compiler here is the link: https://repl.it/@javacrypto/SoHashicorpVaultAesGcmEncryption

    This is a "proof of concept" to show in general how to perform an encryption but it lacks some critical points that I'm to lazy to make your work :-).

    1. This example encrypts a string to an encrypted file - you will need to get the original data from a file
    2. Having a large file you may encounter an "out of memory" error as all operations with your data are done in your heap - for a simple calculation you will need a free memory of 4.5 * original data because you take the original data into memory, second time you have the encrypted data in memory, third time you're copying the encrypted data to ciphertextWithNonce and in the end (number 4) you encode all data to a base64-String. For large programs you will need to switch to a "chunk wise" encryption, done with CiphertextOutputStream
    3. To make the Base64-writing of the complete data a little more convenient I recommend the additional usage of Apache's Base64OutputStream (available via Maven https://mvnrepository.com/artifact/commons-codec/commons-codec).

    Security warning: this code has no exception handling and is for educational purpose only.

    code:

    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.spec.GCMParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.security.SecureRandom;
    import java.util.Base64;
    
    public class Hashicorp_Aes_Gcm_encryption {
        public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, IOException {
            System.out.println("Hashicorp Vault AES GCM encryption");
            // https://stackoverflow.com/questions/64714527/how-can-i-encrypt-data-with-an-already-generated-aes-256-gcm-96-key-coming-from
            // paste your key here:
            String keyBase64 = "VxJWkOYm2F5z1nF1th9zreS6ZAZMFkCq0c/Ik460ayw=";
            // filename with ciphertext for upload
            String filename = "hashicorp_test.enc";
    
            // my sample plaintext
            String plaintext = "The quick brown fox jumps over the lazy dog";
    
            // aes gcm encryption
            // decode key
            byte[] key = Base64.getDecoder().decode(keyBase64);
            // generate random nonce
            byte[] nonceRandom = new byte[12];
            SecureRandom secureRandom = new SecureRandom();
            secureRandom.nextBytes(nonceRandom);
            // calculate specs
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * 8, nonceRandom);
            // initialize cipher
            Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");//NOPadding
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
            // encrypt
            byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
            // concentenate iv + ciphertext
            int ciphertextWithNonceLength = nonceRandom.length + ciphertext.length;
            byte[] ciphertextWithNonce = new byte[ciphertextWithNonceLength];
            System.arraycopy(nonceRandom, 0, ciphertextWithNonce, 0, nonceRandom.length);
            System.arraycopy(ciphertext, 0, ciphertextWithNonce, nonceRandom.length, ciphertext.length);
            String ciphertextBase64 = Base64.getEncoder().encodeToString(ciphertextWithNonce);
            System.out.println("ciphertext: " + ciphertextBase64);
            // save encrypted data to a file
            Files.write(Paths.get(filename), ciphertextBase64.getBytes(StandardCharsets.UTF_8));
            System.out.println("ciphertext written to file: " + filename);
            System.out.println("used key: " + keyBase64);
        }
    }