Java AES CTR加密输入中的查找

Java AES CTR加密输入中的查找,java,encryption,aes,random-access,Java,Encryption,Aes,Random Access,由于CTR模式下的AES非常适合随机访问,因此假设我在AES-CTR模式下使用CipherOutputStream创建了一个数据源。下面的库不是我的,它使用一个RandomAccessFile,允许查找文件中的特定字节偏移量 我最初的想法是使用CipherInputStream和Cipher一起使用正确的参数初始化,但不执行查找和状态,以不支持标记和重置 我是否应该研究一下CTR的IV/block计数器的配置,并使用自定义输入流(听起来像是针对self的霰弹枪)重新创建该计数器,我是否错过了一部

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

我最初的想法是使用
CipherInputStream
Cipher
一起使用正确的参数初始化,但不执行查找和状态,以不支持
标记
重置


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

我最终找到了在CTR模式下IV是如何更新的。事实证明,对于它处理的每个AES块,它都执行一个简单的+1。我按照下面的思路进行阅读

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

  • 块大小
    :固定为16(128位,AES块大小)
  • cipher
    :一个
    javax.crypto.cipher
    的实例,初始化为处理AES
  • delegate
    :封装允许随机访问的加密资源的
    java.io.InputStream
  • input
    :我们将提供读取服务(流将负责解密)
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
}
void seek(长位置){
//计算包含我们需要查找的字节的块号
长块=位置/块大小;
//分配一个16字节的缓冲区
ByteBuffer缓冲区=ByteBuffer.allocate(块大小);
//用原始IV(IV减去实际计数器值)填充前12个字节
put(cipher.getIV(),0,块大小为-4);
//将IV的计数器设置为计算的块索引+1(计数器从1开始)
buffer.putInt(块+1);
IvParameterSpec iv=新的IvParameterSpec(buffer.array());
//使用新的IV重新初始化密码实例
cipher.init(cipher.ENCRYPT_模式,密钥,iv);
//查找委托包装器(如RandomAccessFile中的seek()),然后
//重新创建要从新位置读取的委托流)
//重新创建我们提供读取服务的输入流
输入=新的CipherInputStream(委托,密码);
//下一次读取将在块边界,需要跳过一些字节才能到达pos
int toSkip=(int)(位置%BLOCK_大小);
字节[]垃圾=新字节[toSkip];
//根据需要将字节读入垃圾数组(应为最大块大小)
//字节
int skipped=input.read(垃圾,0,toSkip);
while(跳过
请注意,此处省略了查找委托资源,因为这取决于委托
InputStream
下面的内容。还请注意,初始IV需要在计数器1(最后4个字节)处启动


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

CTR中的计数器应初始化为0,如果它滚动,则可能会使用相同的
nonce | | CTR
值重用相同的密钥,这对安全性是灾难性的。你应该在那之前重新键入。你是对的,我在这段时间内对我的实现做了一些修改,以符合规范(不确定为什么我没有找到/去做…)嗯,我不会再编辑注释了。根据我的观察,OracleJDK将整个IV字节序列作为一个大整数,而不仅仅是尾随的32位整数。字节[]ivBytes=新字节[16];数组填充(ivBytes,(byte)-1);IvParameterSpec iv=新的IvParameterSpec(ivBytes);cipher.init(cipher.ENCRYPT_模式,密钥,iv);cipher.update(新字节[16]);字节[]o2=cipher.update(新字节[16]);数组.fill(ivBytes,(byte)0);iv=新的IvParameterSpec(ivBytes);cipher.init(cipher.ENCRYPT_模式,密钥,iv);字节[]o3=密码更新(新字节[16]);Assert.assertarayequals(o2,o3);>/将IV的计数器设置为计算的块索引+1(计数器从1开始),这是不正确的。IV是计数器,不仅仅是最后四个字节。请参阅@fishfaulty>中的示例,CTR中的计数器应初始化为0。这是不正确的。无论您在
[0..2^128-1]
中的起始值是多少,您都将在执行2^128个步骤后重新使用它。