Java AES CTR加密输入中的查找
由于CTR模式下的AES非常适合随机访问,因此假设我在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的霰弹枪)重新创建该计数器,我是否错过了一部
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个步骤后重新使用它。