Поиск в AES-CTR-зашифрованном входе

Как AES в режиме CTR отлично подходит для произвольного доступа, скажем, у меня есть источник данных, созданный с помощью CipherOutputStream в режиме AES-CTR. В библиотеке внизу, которая не является моей, используется RandomAccessFile, которая позволяет искать конкретное смещение байта в файле.

Моя первоначальная мысль заключалась в том, чтобы использовать CipherInputStream с Cipher, инициализированный правильными параметрами, но API для этого не занимается поиском и не поддерживает mark и reset.

Есть ли какая-то часть API, которую я пропустил, что может сделать это для меня, следует ли мне изучить конфигурацию счетчика CTR IV/block и воссоздать это с помощью пользовательского потока ввода (который звучит как дробовик, нацеленный на self мне) или взять какой-то другой подход, который я пропустил?

Ответ 1

Справа, заметил, что для этого я получил значок Tumbleweed, "yay"...

В итоге я понял, как обновляется IV в режиме CTR. Это, как оказалось, делает простой +1 для каждого блока AES, который он обрабатывает. Я выполнил чтение в следующих строках.

Учитывая класс, реализующий метод read, который будет читать следующий байт в байтовой последовательности, которая зашифровывается и должна поддерживать поиск в этой последовательности и следующих переменных:

  • BLOCK_SIZE: фиксировано на 16 (128 бит, размер блока AES);
  • cipher: экземпляр javax.crypto.Cipher, инициализированный для работы с AES;
  • delegate: a java.io.InputStream, который обертывает зашифрованный ресурс, который позволяет произвольный доступ;
  • input: a javax.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 байта).

Unittests показывают, что этот подход работает (тесты производительности будут выполняться в какой-то момент в будущем:)).