Seeking in AES-CTR-encrypted input

前端 未结 1 608
挽巷
挽巷 2021-02-06 00:46

As AES in CTR mode is great for random access, lets say I have a data source created with a CipherOutputStream in AES-CTR mode. The library underneath—which is not

相关标签:
1条回答
  • 2021-02-06 01:24

    I ended up looking up exactly how the IV is updated in CTR mode. This turns out to do a simple +1 for each AES block it processes. I implemented reading along the following lines.

    Given a class that implements a read-like method that would read the next byte in a byte sequence that is encrypted and needs to support seeking in that sequence and the following variables:

    • BLOCK_SIZE: fixed at 16 (128 bits, AES block size);
    • cipher: an instance of javax.crypto.Cipher, initialized to deal with AES;
    • delegate: a java.io.InputStream that wraps an encrypted resource that allows random access;
    • input: a javax.crypto.CipherInputStream we'll be serving reads from (the stream will take care of the decryption).

    The seek method is implemented as such:

    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
    }
    

    Note that seeking the delegate resource is omitted here, as this depends on what is underneath the delegate InputStream. Also note that the initial IV is required to be started at counter 1 (the last 4 bytes).

    Unittests show that this approach works (performance benchmarks will be done at some point in the future :)).

    0 讨论(0)
提交回复
热议问题