Как я могу зашифровать данные с помощью уже сгенерированного ключа AES 256 GCM 96 (поступающего из Hashicorp Vault)?

У меня есть строка, представляющая симметричный ключ, полученный с помощью Hashicorp Vault (на самом деле это может быть не важно). Мне нужен этот ключ для шифрования больших файлов, поэтому я не могу отправить файл напрямую в Vault с просьбой зашифровать данные. Вместо этого я хочу сделать это локально, поэтому я попросил Vault создать для меня симметричный ключ (используя транзит / datakey / plaintext / endpoint). Теперь у меня есть симметричный ключ (и его зашифрованный текст) длиной 44 байта, созданный с помощью алгоритма aes256_gcm96. Итак, мой 32-байтовый ключ обернут 96-битным (12 байтовым) блоком gcm, насколько я понял. Теперь я хочу использовать этот ключ для шифрования моих данных, но ключ слишком длинный для этого, поэтому мне нужно как-то либо развернуть его, либо вызвать некоторую функцию, которая принимает на входе такой ключ. Я пытался использовать Cipher для шифрования своих данных. Это то, что я (ошибочно) делал до сих пор

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);`

При вызове функции init, очевидно, выдается исключение: java.security.InvalidKeyException: ключ должен быть 32 байта

Какую операцию я могу выполнить, чтобы получить действующий ключ?

Спасибо.


person Apokalos    schedule 06.11.2020    source источник
comment
stackoverflow.com/a/53015144/1235935   -  person Saptarshi Basu    schedule 06.11.2020
comment
Спасибо за полезную ссылку!   -  person Apokalos    schedule 10.11.2020


Ответы (1)


Вы уже получили ссылку от @Saptarshi Basu, которая в общих чертах показывает, как шифровать данные с помощью AES GCM. Как вы видите в моем коде, в этом нет ничего мистического, но есть некоторые ловушки, на которые можно наткнуться.

Начнем с самой важной информации - что такое ключ шифрования? От Hashicorp вы получили строку длиной 44 байта, которая представляет собой чистый 32-байтовый ключ AES GCM, но в кодировке Base64. Чтобы получить ключ, пригодный для использования с шифрованием Java, вам необходимо декодировать ключ в массив байтов следующим образом:

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

Вторая информация, которая нам нужна, - это режим AES - вы правильно назвали его режимом AES GCM, а поскольку вы предоставляете Java ключ длиной 32 байта = 256 бит, это запрашиваемый алгоритм / режим AES GCM 256.

Для шифрования AES GCM необходим третий параметр - nonce (иногда называемый вектором инициализации). Hashicorp говорит вам использовать одноразовый номер длиной 96 бит = 12 байт. По соображениям безопасности важно использовать другой одноразовый номер каждый раз при шифровании, поэтому рекомендуется использовать (безопасный) случайно сгенерированный одноразовый номер:

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

Теперь мы готовы к шифрованию и объединению всех данных, выполняем шаг .doFinal и получаем массив байтов с зашифрованным текстом. Но остановитесь - нам нужно объединить использованный одноразовый номер и зашифрованный текст в более крупный ciphertextWithNonce следующим образом:

nonce | ciphertext

просто скопировав одноразовый номер и зашифрованный текст в новый массив байтов. Затем этот ciphertextWithNonce кодируется в Base64 в окончательный ciphertextBase64 и для целей загрузки записывается в файл.

Если вы вставите свой собственный ключ в начало программы и запустите его, вы получите файл с именем hashicorp_test.enc, готовый к загрузке по вашей вине.

Это пример вывода (ваш будет отличаться из-за случайного элемента):

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

Если вы хотите увидеть, как этот код работает в онлайн-компиляторе, вот ссылка: https://repl.it/@javacrypto/SoHashicorpVaultAesGcmEncryption

Это доказательство концепции, чтобы показать в общих чертах, как выполнять шифрование, но в нем отсутствуют некоторые важные моменты, из-за которых мне было бы лень выполнять вашу работу :-).

  1. В этом примере строка шифруется в зашифрованный файл - вам нужно будет получить исходные данные из файла.
  2. Имея большой файл, вы можете столкнуться с ошибкой нехватки памяти, поскольку все операции с вашими данными выполняются в вашей куче - для простых вычислений вам понадобится свободная память размером 4,5 * исходных данных, потому что вы берете исходные данные в память во второй раз у вас есть зашифрованные данные в памяти, третий раз вы копируете зашифрованные данные в ciphertextWithNonce и в конце (номер 4) кодируете все данные в base64-String. Для больших программ вам нужно будет переключиться на шифрование по частям, выполненное с помощью CiphertextOutputStream.
  3. Чтобы сделать запись полных данных с помощью Base64 немного более удобной, я рекомендую дополнительно использовать Apache Base64OutputStream (доступный через Maven https://mvnrepository.com/artifact/commons-codec/commons-codec).

Предупреждение безопасности: этот код не обрабатывает исключения и предназначен только для образовательных целей.

код:

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);
    }
}
person Michael Fehr    schedule 06.11.2020
comment
Вы успешно запустили шифрование? Тогда отметьте мой ответ как принятый, спасибо. - person Michael Fehr; 09.11.2020
comment
Да! Очень полезно! Спасибо :) - person Apokalos; 10.11.2020
comment
Просто вопрос: поскольку я собираюсь зашифровать большие файлы, записывая их в буфер, могу ли я объединить одноразовый номер и зашифрованный файл, например, зашифрованный текст | nonce вместо nonce | зашифрованный текст? Есть ли какая-то особая причина делать это так, как вы показали? Спасибо - person Apokalos; 10.11.2020
comment
Выполняя собственное шифрование, вы можете делать все, что хотите, но вы работаете со сторонним поставщиком, использующим nonce | зашифрованный текст в их API. Кстати. Показанная версия - это способ, которым работает почти каждая программа. - person Michael Fehr; 10.11.2020