10

由于 CTR 模式下的 AES 非常适合随机访问,假设我有一个CipherOutputStream在 AES-CTR 模式下创建的数据源。下面的库(不是我的)使用RandomAccessFile允许查找文件中特定字节偏移量的 a。

我最初的想法是使用 aCipherInputStreamCipher使用正确的参数进行初始化,但是用于此的 API不进行搜索,并且声明不支持markand reset

是否有我错过的 API 的一部分可以为我做到这一点,我是否应该查看 CTR 的 IV/块计数器的配置并使用自定义输入流重新创建它(这听起来像是针对self我的霰弹枪)或采取我错过的其他方法?

4

1 回答 1

10

我最终查明了 IV 在 CTR 模式下是如何更新的。事实证明,它为它处理的每个 AES 块做一个简单的 +1。我按照以下思路实现了阅读。

给定一个实现类似read- 方法的类,该方法将读取加密字节序列中的下一个字节,并且需要支持在该序列中查找和以下变量:

  • BLOCK_SIZE:固定为 16(128 位,AES 块大小);
  • cipher: 的一个实例javax.crypto.Cipher,初始化为处理 AES;
  • delegate:java.io.InputStream包装允许随机访问的加密资源;
  • input:ajavax.crypto.CipherInputStream我们将提供读取服务(流将负责解密)。

seek方法是这样实现的:

void seek(long pos) {
    // calculate the block number that contains the byte we need to seek to
    long block = pos / BLOCK_SIZE;
    // allocate a 16-byte buffer
    ByteBuffer buffer = ByteBuffer.allocate(BLOCK_SIZE);
    // fill the first 12 bytes with the original IV (the iv minus the actual counter value)
    buffer.put(cipher.getIV(), 0, BLOCK_SIZE - 4);
    // set the counter of the IV to the calculated block index + 1 (counter starts at 1)
    buffer.putInt(block + 1);
    IvParameterSpec iv = new IvParameterSpec(buffer.array());
    // re-init the Cipher instance with the new IV
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    // seek the delegate wrapper (like seek() in a RandomAccessFile and 
    // recreate the delegate stream to read from the new location)
    // recreate the input stream we're serving reads from
    input = new CipherInputStream(delegate, cipher);
    // next read will be at the block boundary, need to skip some bytes to arrive at pos
    int toSkip = (int) (pos % BLOCK_SIZE);
    byte[] garbage = new byte[toSkip];
    // read bytes into a garbage array for as long as we need (should be max BLOCK_SIZE
    // bytes
    int skipped = input.read(garbage, 0, toSkip);
    while (skipped < toSkip) {
        skipped += input.read(garbage, 0, toSkip - skipped);
    }

    // at this point, the CipherStream is positioned at pos, next read will serve the 
    // plain byte at pos
}

请注意,这里省略了寻找委托资源,因为这取决于委托下面的内容InputStream。另请注意,初始 IV 需要从计数器 1(最后 4 个字节)开始。

单元测试表明这种方法是有效的(性能基准将在未来的某个时候完成:))。

于 2013-05-23T09:55:30.997 回答