【问题标题】:How are the IV and authentication tag handled for "AES/GCM/NoPadding"?如何处理“AES/GCM/NoPadding”的 IV 和身份验证标签?
【发布时间】:2015-10-29 08:33:49
【问题描述】:

我在 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

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


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

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 对我以这种方式使用安全吗?
  • 它是否避免了在 Galois/Counter 模式下重复使用 IV、组合键的灾难?
  • 当我有多个应用程序同时运行此代码时仍然安全吗?所有应用程序都从同一 src 数据(可能在同一毫秒内)向用户显示加密消息?
  • 返回的 IV 是由什么制成的?它是一个原子计数器加上一些随机噪声吗?
  • 我是否需要避开 cipher.getIV() 并使用自己的计数器自己构建 IV?
  • 假设我使用的是 Oracle JDK 8 + JCE Unlimited Strength 扩展,实现cipher.getIV() 的源代码是否可以在线获得?
  1. 那个 IV 总是 12 字节长吗?

  2. 身份验证标签是否总是 16 字节(128 位)长?

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

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

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

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

(因为这些问题都是关于我自己的代码的同一块,并且它们彼此密切相关,并且其他人在实现相同的功能时可能/应该有相同的问题集,因此拆分感觉是错误的问题分成多个帖子。如果这更适合 StackOverflow 的格式,我可以单独重新发布它们。让我知道!)

【问题讨论】:

  • "对于给定的键,IV 不能重复。"显然不能,因为 Java 不会跟踪您已经为给定密钥使用了哪些 IV(随机数)。问题是对于给定的“neglibile”定义,重复随机数的概率是否可以忽略不计。
  • 我确实发现你不应该在一篇文章中问 16 个(!)问题。你真的应该对Cryptography 进行一些研究,以减少问题的数量。将它们分成多个帖子不是一种选择,因为它们中的大多数都与编程无关。
  • 如果您认为这会使帖子对其他人更有用,我可以删除一些问题。我想在这里问他们所有人是自私的。回复:“不得重复” - 同意。主要是我想知道 cipher.getIV() 是否给了我一些公然违反 RFC 的东西,以及我是否最好从头开始创建自己的 IV。

标签: java security encryption cryptography aes-gcm


【解决方案1】:

Q1:cipher.getIV() 返回的 IV 对我这样使用安全吗?

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

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


Q2: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 数据加密是否安全?

查看 Q5 和 Q7 的答案


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

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


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

【讨论】:

  • 非常感谢!所以,我的代码是“正确的”,但不可移植——在不同的 Java 安装上,它会做不同的事情——我应该通过在加密期间提供我自己的 GCMParameterSpec 来解决这个问题。 (如果我错了,请纠正我。)感谢提示:随机填充。对不起,我得去删除一些无用的代码。 ;)
  • 不,我猜你明白了。只要使用new SecureRandom()就足够了。
猜你喜欢
  • 2016-08-24
  • 2013-07-17
  • 2016-11-14
  • 1970-01-01
  • 2014-07-14
  • 2020-05-29
  • 1970-01-01
  • 2018-02-19
  • 2021-09-24
相关资源
最近更新 更多