1
votes

How do you decrypt aes 128 ctr encrypted file from the middle for http range support? Here is the encrypted file: https://www.dropbox.com/s/8e9qembud6n3z7i/encrypted.txt?dl=0

the key is base64 encrypted: E7VQWj3cv1JUi5pklirtDQ9SRJt1DhiqYgzPSpIiVP0

Mega docs: https://mega.co.nz/#doc

The IV is calculated by decrypting the key which gives an array:

Array
(
    [0] => 330649690
    [1] => 1037877074
    [2] => 1418435172
    [3] => 2519395597
    [4] => 257049755
    [5] => 1963858090
    [6] => 1645006666
    [7] => 2451723517
)

The IV is obtained by slicing the array at 4th offset with length of two And the last two elements of the array are filled with 0:

Array
(
    [0] => 257049755
    [1] => 1963858090
    [2] => 0
    [3] => 0
)

Then the key is XOR'd and made into a 128bit array which is then converted into string by the php function pack:

 $key = array($key[0] ^ $key[4], $key[1] ^ $key[5], $key[2] ^ $key[6], $key[3] ^ $key[7]);
 $key = base64_encode(a32_to_str($key));
 $iv = base64_encode(a32_to_str($iv));

Then the file is decrypted using the normal php aes library. I am using mcrypt_generic for the decryption process. The problem arises when I try to decrypt the file from 2nd byte or the 3rd or the middle. It works fine if I decrypt it from the 1st byte.

Another thing I have noticed is, If I decrypt the file from 2nd byte, but before that, I decrypt a random string or just the digit 0, the decryption works from the 2nd byte then. I suppose it has something to do with the IV block counter. I decrypt a random byte then continue decrypting the actual cipher so it works. I need to start decrypting the file from the start, lets say from the 40mb offset to support live strem seeking. But that would consume too much memory because I will have to decrypt 40mb of 0's before seeking can be done. How can I move the IV counter value to 40mb offset ??

I read that IV is increased by +1 for each block for decryption. But since my IV is an array I have tried everything it does not work if I add 1 in it. I've been at it for months with no fruit. Please help

Here is my previous question which helped understanding the process a bit: AES 128 bit CTR partial file decryption with PHP

1

1 Answers

0
votes

Your initial research is indeed correct. In CTR mode, the IV (or nonce) is simply incremented by 1 after each encryption operation. (Encryption and decryption are the same operation in CTR mode, so you can substitute one word for the other as necessary.)

In other words, the state of a CTR mode cipher can be predicted in advance – just add the number of blocks already encrypted to the initial IV. In particular, the state does not depend on the plaintext in any way. AES has a block size of 16, so you would add the number of bytes encrypted divided by 16.

The IV can be considered a 128-bit integer stored in big endian. The cryptography API you use represents it as an array of four 32-bit integers. Simply add the number of blocks to the fourth integer before initializing the cipher. If you think you'll need to handle more than four billion blocks or so, you need to add handling for overflow to the third integer.

The slightly trickier part is initializing the cipher to a state where you have already encrypted a number of bytes that is not divisible by the block size. The solution is to first initialize the cipher to the number of bytes already encrypted divided by 16, rounded down, and then encrypting (the number of bytes already encrypted mod 16) dummy bytes. I believe this is in fact what you already suspected.

You're writing in PHP, but I'm posting a method from a Mega downloader program which I've written in Java in case it helps:

public Cipher getDownloadCipher(final long startPosition) throws Exception {
    final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
    final ByteBuffer buffer = ByteBuffer.allocate(16).put(nonce);
    buffer.asLongBuffer().put(startPosition / 16);
    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(buffer.array()));
    final int skip = (int) (startPosition % 16);
    if (skip != 0) {
        if (cipher.update(new byte[skip]).length != skip) {
            //that should always work with a CTR mode cipher
            throw new IOException("Failed to skip bytes from cipher");
        }
    }
    return cipher;
}