【问题标题】:How can I encrypt data with an already generated AES 256 GCM 96 key (coming from Hashicorp Vault)?如何使用已生成的 AES 256 GCM 96 密钥(来自 Hashicorp Vault)加密数据?
【发布时间】:2020-11-06 12:16:26
【问题描述】:

我有一个表示对称密钥的字符串,它是通过使用 Hashicorp Vault 获得的(这实际上可能并不重要)。我需要这个密钥来加密大文件,所以我不能直接将文件发送到 Vault 要求它加密数据。我想在本地进行,所以我要求 Vault 为我创建一个对称密钥(通过使用 transit/datakey/plaintext/ 端点)。我现在有一个 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: The key must be 32 bytes

我可以做什么样的操作来获得一个有效的密钥?

谢谢。

【问题讨论】:

标签: java encryption aes-gcm symmetric-key vault


【解决方案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 字节长的随机数。出于安全原因,每次加密时使用不同的 nonce 非常重要,因此最好使用(安全的)随机生成的 nonce:

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

现在我们准备好加密并将所有数据放在一起,执行“.doFinal”步骤,我们会收到一个带有密文的字节数组。但是停止 - 我们需要以这种方式将使用的 nonce 和密文连接到更大的 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 字符串。对于大型程序,您需要 切换到“块明智”加密,使用 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);
    }
}

【讨论】:

  • 你运行加密成功了吗?那么请将我的答案标记为“已接受”,谢谢。
  • 只是一个问题:因为我要通过写入缓冲区来加密大文件,我可以将 nonce 和加密文件连接起来,如“ciphertext | nonce”而不是“nonce | ciphertext”?有什么特别的理由以你展示的方式去做吗?谢谢
  • 在进行“自己的”加密时,您可以“做你想做的”,但您正在与在其 API 中使用“nonce | ciphertext”的第 3 方合作。顺便提一句。显示的版本是几乎每个程序都使用的方式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-07
  • 2021-09-03
  • 2013-06-16
  • 2019-07-19
  • 1970-01-01
  • 2017-02-12
  • 1970-01-01
相关资源
最近更新 更多