0

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.

Apokalos
  • 124
  • 1
  • 2
  • 13

1 Answers1

1

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);
    }
}
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40
  • Did you run the encryption successfully? Then kindly mark my answer as "accepted", thanks. – Michael Fehr Nov 09 '20 at 10:28
  • Just a question: since I'm going to cipher big files by writing in a buffer, can I concatenate the nonce and the ciphered file like "ciphertext | nonce" instead of "nonce | ciphertext"? Is there any particular reason to do it in the way you showed? Thank you – Apokalos Nov 10 '20 at 11:22
  • When doing your "own" encryption you could "do what you want" but you are working with a 3rd party that uses "nonce | ciphertext" in their API. B.t.w. the showed version is the way nearly every program is working with. – Michael Fehr Nov 10 '20 at 12:23