"AES/GCM/NoPadding" 的 IV 和身份验证标签是如何处理的?

How are the IV and authentication tag handled for "AES/GCM/NoPadding"?

我在 Java 8 中使用 AES/GCM/NoPadding 加密,我想知道我的代码是否存在安全漏洞。我的代码似乎 有效 ,因为它加密和解密文本,但一些细节不清楚。

我的主要问题是:

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV(); // ?????

该 IV 是否满足“对于给定密钥,IV 不得重复”的要求。来自 RFC 4106?

对于我的相关问题(见下文)的任何答案/见解,我也很感激,但第一个问题最让我烦恼。我不知道在哪里可以找到回答这个问题的源代码或文档。


这是完整的代码。如果我在写这篇文章时引入了错误,我深表歉意 post:

class Encryptor {
  Key key;

  Encryptor(byte[] key) {
    if (key.length != 32) throw new IllegalArgumentException();
    this.key = new SecretKeySpec(key, "AES");
  }

  // the output is sent to users
  byte[] encrypt(byte[] src) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    byte[] iv = cipher.getIV(); // See question #1
    assert iv.length == 12; // See question #2
    byte[] cipherText = cipher.doFinal(src);
    assert cipherText.length == src.length + 16; // See question #3
    byte[] message = new byte[12 + src.length + 16]; // See question #4
    System.arraycopy(iv, 0, message, 0, 12);
    System.arraycopy(cipherText, 0, message, 12, cipherText.length);
    return message;
  }

  // the input comes from users
  byte[] decrypt(byte[] message) throws Exception {
    if (message.length < 12 + 16) throw new IllegalArgumentException();
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    GCMParameterSpec params = new GCMParameterSpec(128, message, 0, 12);
    cipher.init(Cipher.DECRYPT_MODE, key, params);
    return cipher.doFinal(message, 12, message.length - 12);
  }
}

假设用户破解了我的秘钥=游戏结束。


更详细的问题/相关问题:

  1. 我以这种方式使用 cipher.getIV() 返回的 IV 安全吗?
  1. IV 总是 12 字节长吗?

  2. 身份验证标记是否始终为 16 字节(128 位)长?

  3. 使用#2 和#3,并且没有填充,这是否意味着我的加密消息总是 12 + src.length + 16 字节长? (这样我就可以安全地将它们压缩成一个字节数组,我知道正确的长度?)

  4. 给定用户知道的恒定 src 数据,我向用户显示无限数量的 src 数据加密是否安全?

  5. 如果 src 数据每次都不同(例如包括 System.currentTimeMillis() 或随机数),我向用户显示无限数量的 src 数据加密是否安全?

  6. 如果我在加密前用随机数填充 src 数据会有帮助吗?说前后8个随机字节,还是只在一端?还是那根本没有帮助/使我的加密变得更糟?

(因为这些问题都是关于我自己代码的同一个block,并且彼此之间有很强的相关性,而其他人might/should在实现相同功能时也有同一套问题,所以感觉将问题拆分为多个 post 是错误的。如果这更适合 Whosebug 的格式,我可以单独重新 post 它们。让我知道!)

Q1:cipher.getIV() 返回的 IV 对我来说安全吗?

是的,它至少是为 Oracle 提供的实现。它是使用默认 SecureRandom 实现单独生成的。因为它的大小是 12 个字节(GCM 的默认值),所以你有 96 位的随机性。计数器重复的机会非常小。您可以在 Oracle JDK 所基于的 OpenJDK(GPL'ed)中查找源代码。

不过,我仍然建议您生成自己的 12 个随机字节,因为其他提供商的行为可能有所不同。


问题 2:IV 总是 12 字节长吗?

极有可能,因为它是 GCM 默认值,但其他长度 对 GCM 有效。但是,该算法必须对 12 字节以外的任何其他大小进行额外计算。由于弱点,强烈建议将其保持在 12 字节/96 位,并且 API 的 可能会限制您选择 IV 大小


Q3:认证标签总是16字节(128位)长吗?

不,它可以有任何字节大小,从 64 位到 128 位,递增 8 位。如果它更小,它只包含身份验证标记的最左边的字节。您可以使用 GCMParameterSpec 作为您的 init 调用的第三个参数指定另一个标签大小。

请注意,GCM 的强度在很大程度上取决于标签的大小。我建议将其保留为 128 位。 96 位应该是最小值 特别是 如果你想生成大量密文。


Q4:有了#2 和#3,并且没有填充,这是否意味着我的加密消息总是 12 + src.length + 16 字节长? (所以我可以安全地将它们压缩成一个字节数组,我知道正确的长度?)

见上文。对于 Oracle 提供商来说就是这种情况。使用GCMParameterSpec来确定它。


Q5:给定用户知道的恒定 src 数据,我向用户显示无限数量的 src 数据加密是否安全?

几乎 未绑定,是的。大约 2^48 次加密后我会开始担心。不过,一般来说,您应该 密钥更改进行设计。


Q6:如果 src 数据每次都不同(例如包括 System.currentTimeMillis() 或随机数),我向用户显示无限数量的 src 数据加密是否安全)?

查看问题 5 和问题 7 的答案


Q7:如果我在加密前用随机数填充src数据会有帮助吗?说前后8个随机字节,还是只在一端?还是那根本没有帮助/使我的加密变得更糟?

不,这根本没有帮助。 GCM 在底层使用 CTR 模式,所以它只会用密钥流加密。它不会充当IV。现在,如果您要加密不断变化的消息,您可以查看 AES-GCM-SIV,但请注意,该算法未在任何 JCA 提供程序中实现。


如果您需要大量密文(高于 2^48!或 2^32 - ~40 亿 - 谨慎起见)那么我建议您使用该随机数和您的密钥作为密钥派生函数或 KDF。 HKDF 目前是最好的,但您可能需要使用 Bouncy Castle 或自己实现它。