Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Seeking in AES-CTR-encrypted input

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 mine—uses a RandomAccessFile that allows to seek to a specific byte offset in the file.

My initial thought would be to use a CipherInputStream with a Cipher initialized with the right parameters, but the API for that doesn't do seeking and states to not support mark and reset.

Is there a part of the API that I've missed that can do this for me, should I look into the configuration of CTR's IV/block counter and recreate that with a custom input stream (which sounds like shotgun aimed at self to me) or take some other approach I've missed?

like image 294
akaIDIOT Avatar asked May 16 '13 08:05

akaIDIOT


People also ask

How does AES CTR mode work?

The way encryption works in AES CTR mode is that we generate some random bits with the encryption key provided and the IV. With these random bits we then XOR them with our string. This creates a randomized text.

Is AES 128 CTR secure?

Out of 128-bit, 192-bit, and 256-bit AES encryption, which progressively use more rounds of encryption for improved security, 128-bit AES encryption is technically the least secure.

Does AES CTR use padding?

AES-CTR mode does not require plaintext padding. However, ESP does require padding to 32-bit word-align the authentication data.

Is AES CTR a stream cipher?

CTR (short for counter) is a popular AES block cipher mode in which every step can be done in parallel. CTR is similar to OFB as it also involves XOR-ing a sequence of pad vectors with the plaintext and ciphertext blocks.


1 Answers

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 :)).

like image 157
akaIDIOT Avatar answered Oct 10 '22 16:10

akaIDIOT