【问题标题】:Java AES Counter Mode IncrementingJava AES 计数器模式递增
【发布时间】:2017-09-17 14:22:11
【问题描述】:

我使用this answer 中的示例代码用Java 编写了一个加密/解密原型。但是,我正在尝试使用 AES 的计数器模式 (CTR),加密值似乎与我尝试加密的整数序列一样可递增。

考虑我的原型的以下输出:

i = 0: enc='5941F8', dec='000', length=6
i = 1: enc='5941F9', dec='001', length=6
i = 2: enc='5941FA', dec='002', length=6
i = 3: enc='5941FB', dec='003', length=6
i = 4: enc='5941FC', dec='004', length=6
i = 5: enc='5941FD', dec='005', length=6
i = 6: enc='5941FE', dec='006', length=6
i = 7: enc='5941FF', dec='007', length=6
i = 8: enc='5941F0', dec='008', length=6
i = 9: enc='5941F1', dec='009', length=6
i = 10: enc='5940F8', dec='010', length=6
i = 11: enc='5940F9', dec='011', length=6
i = 12: enc='5940FA', dec='012', length=6

请注意enc 值通常与dec 值仅相差一个数字。 AES 计数器模式生成的加密值通常是可迭代的/彼此相似的还是我做错了什么?

到目前为止,我已经尝试过使用不同的加密密钥、初始化向量、填充方案、更长/更短的整数序列(从不同的值开始)等,但到目前为止似乎没有任何效果。此外,到目前为止,Google 和其他关于计数器模式下 Java AES 密码的 SO 问题几乎没有用处。请记住,我是加密新手。

我的原型代码如下:

public class Encryptor {
  public static String encrypt(String key, String initVector, String value) {
    try {
      IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
      SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

      Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING");
      cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

      byte[] encrypted = cipher.doFinal(value.getBytes());
      return DatatypeConverter.printHexBinary(encrypted);
    }
    catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }

  public static String decrypt(String key, String initVector, String encrypted) {
    try {
      IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
      SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

      Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING");
      cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

      byte[] decrypted = cipher.doFinal(DatatypeConverter.parseHexBinary(encrypted));
      return new String(decrypted);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    return null;
  }

  public static void main(String[] args) {
    String key = "Bar12345Bar12345"; // 128 bit key
    String initVector = "RandomInitVector"; // 16 bytes IV

    System.out.println(decrypt(key, initVector,
        encrypt(key, initVector, "Hello World")));
    for (int i = 0; i < 1000; ++i) {
      String encrypted = encrypt(key, initVector, StringUtils.leftPad("" + i, 3, '0'));
      String decrypted = decrypt(key, initVector, encrypted);
      int encLen = encrypted.length();
      System.out.println("i = " + i + ": enc='" + encrypted + "', dec='" + decrypted + "', length=" + encLen);
    }
  }
}

【问题讨论】:

  • 有趣的是,我对您链接的答案投了反对票。你也应该。在 CBC 模式下使用相同的 IV 是一个问题,但在 CTR 模式下这是一个更大的问题,因此您的问题。
  • 请注意我在链接答案下的评论,这应该阻止您提出这个问题。我是否不够清楚应该做什么?
  • 不,答案很好。当我接受你的建议时,我得到了更好的数据。但是你的回答提出了一个有趣的推论问题。请参阅下面对您的答案的评论。

标签: java encryption cryptography aes ctr-mode


【解决方案1】:

CTR mode is a streaming mode of operation。这意味着随机数和密钥对创建一个唯一的密钥流,然后与明文进行异或。由于 XOR 是对称的,因此对密文进行解密的操作完全相同。

现在,XOR 按位工作。如果您使用相同的密钥和随机数对进行多次加密,则每次都将使用完全相同的密钥流加密明文。这意味着在比较两个结果密文时,两个明文之间不同的位位置也会不同。这称为两次垫或多次垫。

在您的示例中,如果每次迭代对 encdec 进行异或运算,您将始终得到 0x6971C8。这些是密​​钥流的前三个字节。 ASCII 字符 0 是 0x300x59 异或时是 0x69 等等。

解决方案是每次使用相同的密钥加密时使用不同的随机数(使用一次的数字)。随机数(有时称为 IV)应该小于底层块密码的块大小。 AES 的块大小为 16 字节。我们通常选择长度为 8 字节或 12 字节的随机随机数,具体取决于我们要加密多少数据而不会发生计数器冲突。 12 字节的 nonce 似乎是最好的折衷方案,因为您可以生成许多随机 nonce 而不会产生太大的冲突机会,并且您可以使用密钥 + nonce 对加密多达 68 GB 而不会发生反冲突的机会。

【讨论】:

  • 您的解决方案,有效。非常感谢。我基本上使用了我的变量i 的类似版本作为初始化向量。然而,这对我来说提出了一个比它解决的问题更大的问题。我是否正确地推测确实没有可行的方法来解密应该是某个序列中的整数的值?为了解密工作,我需要知道初始化向量。再次感谢!
  • 是与否,nonce 不必随机选择,但必须不同。例如,如果有一个与您要加密的整数相关联的唯一 ID,那么您可以简单地使用该 ID 作为您的 nonce。如果您不希望这样,那么总是有可能使用格式保留加密。请记住,FPE 的实现并不多。
  • 警告:IV 不是随机数,它是 CTR 模式的初始计数器值。随机数应该是例如IV 的第一个(最左侧)8 个字节,其他字节留空,以免与计数器值重叠,即使在使用唯一 IV 时也是如此。这从答案中还不够清楚
猜你喜欢
  • 1970-01-01
  • 2021-05-16
  • 2021-05-22
  • 2013-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多