5

我试图通过自己在这里寻找并在其他地方搜索很长一段时间来找到答案,但我仍然有一些问题。

假设这个 Java 代码:

try
{   
    int cipherMode = Cipher.ENCRYPT_MODE;

    SecretKeySpec secretKey = ...; // generated previously using KeyGenerator       

    byte[] nonceAndCounter = new byte[16];
    byte[] nonceBytes      = ...; // generated previously using SecureRandom's nextBytes(8);

    // use first 8 bytes as nonce
    Arrays.fill(nonceAndCounter, (byte) 0);
    System.arraycopy(nonceBytes, 0, nonceAndCounter, 0, 8);

    IvParameterSpec ivSpec = new IvParameterSpec(nonceAndCounter);
    Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

    cipher.init(cipherMode, secretKey, ivSpec);

    File inFile  = new File(...);
    File outFile = new File(...);

    long bytesRead = 0;

    try (FileInputStream is = new FileInputStream(inFile);
         FileOutputStream os = new FileOutputStream(outFile))
    {       
        byte[] inBuf       = new byte[512 * 1024];
        byte[] outBuf      = new byte[512 * 1024];
        int    readLen     = 0;

        ByteBuffer byteBuffer = ByteBuffer.allocate(8);
        byteBuffer.putLong(bytesRead);

        while ((readLen = is.read(inBuf)) != -1)
        {
            bytesRead += readLen;

            cipher.update(inBuf, 0, readLen, outBuf, 0);

            os.write(outBuf);
        }

        cipher.doFinal(outBuf, 0);
        os.write(outBuf);
        is.close();
        os.close();
    }
    catch (Exception e) {
        System.out.printf("Exception for file: %s\n", e);
    }
} 
catch (Exception e) { 
    System.out.printf("Exception: %s\n", e);
}

我的问题是:

  1. 关于 CTR 模式的计数器更新,上述代码是否被认为可以?具体来说,我自己不会更新计数器。我应该改用下面的 while 循环吗?我尝试了这个,因为我查看了 cipher.getIV() 在循环中返回的内容,但它没有改变,并且 getIV() 的描述也没有详细说明:

        while ((readLen = is.read(inBuf)) != -1)
        {   
            // use offset for last 8 bytes as counter
            byteBuffer.putLong(bytesRead);
            System.arraycopy(byteBuffer.array(), 0, nonceAndCounter, 8, 8);
    
            bytesRead += readLen;
    
            IvParameterSpec ivSpec = new IvParameterSpec(nonceAndCounter);
    
            cipher.init(cipherMode, secretKey, ivSpec);
    
            cipher.update(inBuf, 0, readLen, outBuf, 0);
    
            os.write(outBuf);
        }
    
    1. 我有更多与修改后的 while 循环方法相关的问题。以这种方式调用 cipher.init() 可以吗?我这样做是因为我还没有找到只更新 IV 的方法(实际上是计数器)。

    2. 这么大的块大小可以还是应该做得更小?在这种情况下,它应该有多大?

4

2 回答 2

6
  1. 关于 CTR 模式的计数器更新,上述代码是否被认为可以?

是的。

但是你可能想稍微调整一下 nonce 和 counter 的大小。如果你的随机数只有 64 位长,那么由于生日悖论,你可能会在 2 32 次加密后遇到随机数冲突(如果你接近那个点,概率会增加)。如果您对所有这些加密使用相同的密钥(我的意思是消息/文件而不是块)并且存在冲突,则这被认为是 CTR 模式的灾难性中断,因为它是两次或多次填充。

您应该考虑使用 96 位随机数和 32 位计数器。缺点是您最多只能安全加密 2 32 个块,这相当于每个消息/文件 68 GB。

  1. 我有更多与修改后的 while 循环方法相关的问题。以这种方式调用 cipher.init() 可以吗?

不。

你真的不应该自己更新计数器。请注意,字节和块密码块之间存在不匹配:您建议的计数器更新使用已经处理的字节作为新的计数器值,它比按块计算的自然 CTR 模式计数器前进得更快。您正在耗尽计数器,以便更有可能发生碰撞。例如,如果 nonce 以数字表示时相差 1,那么如果 nonce 部分较短而对应部分较长,则可能会发生重叠。如果随机数是 96 位长,那么您只能安全地加密大小为 68/16 GB = 4.5 GB 的消息/文件。

此外,由于您有字节/块不匹配,这不再是 CTR 模式,您将很难将此代码移植到其他语言。

  1. 这么大的块大小可以还是应该做得更小?在这种情况下,它应该有多大?

不确定您的意思,AES 的固定块大小为 128 位或 16 字节。

如果您指的是输入/输出缓冲区,那么您应该在您选择的平台上对其进行基准测试以确定。它看起来当然没问题。

于 2017-07-20T18:03:16.830 回答
0

而不是像您在此处那样生成 iv/nonce:

// use first 8 bytes as nonce
Arrays.fill(nonceAndCounter, (byte) 0);
System.arraycopy(nonceBytes, 0, nonceAndCounter, 0, 8);

IvParameterSpec ivSpec = new IvParameterSpec(nonceAndCounter);
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

cipher.init(cipherMode, secretKey, ivSpec);

您应该考虑执行以下操作:

Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
// By doing this here w/o an IvParameterSpec, you let the
// cipher initialization create it. Less chance (see what I did there)
// to influence values that should be completely random.
cipher.init(cipherMode, secretKey);
AlgorithmParameters ivSpec = cipher.getParameters();
byte[] nonceAndCounter= ivSpec.getEncoded()

在接收端,当您需要从返回给 nonceAndCounter 的值构造一个 IvParameterSpec 时,然后构建您的解密模式。

于 2017-07-20T11:10:09.047 回答