asmcrypto / asmcrypto.js

JavaScript Cryptographic Library with performance in mind.
MIT License
658 stars 182 forks source link

AES_GCM_Decrypt_process #170

Open Khivar opened 5 years ago

Khivar commented 5 years ago

There is an issue while decrypting multiple chunks :

const decryptor = new asmCrypto.AES_GCM(new Uint8Array(aesKey), new Uint8Array(iv), new Uint8Array(tag), tagLength/8);

const buffer1 = decryptor.AES_GCM_Decrypt_process(new Uint8Array(encrypted.buffer, 0, 512));
const buffer2 = decryptor.AES_GCM_Decrypt_process(new Uint8Array(encrypted.buffer, buffer1.byteLength, 512));

const buffer3 = new ArrayBuffer(buffer1.byteLength + buffer2.byteLength);
const result = new Uint8Array(buffer3);
result.set(new Uint8Array(buffer1))
result.set(new Uint8Array(buffer2), buffer1.byteLength);
const buffer = result.buffer;

First of all, the buffer1 contains 496 bytes and buffer2 contains 512 bytes. So the resulting buffer contains 1008 bytes. When I check against the original decrypted data, the first 512 bytes are correct but after that everything differs.

So it seems to stash the 16 last bytes on the first call in order to use it as the tag on the upcoming AES_GCM_Decrypt_finish call but since i don't call it right away and instead call AES_GCM_Decrypt_process again, it correctly destash those bytes and process them (hence the first 512 correct bytes in the resulting buffer and not just 496 correct bytes) and again the last 16 bytes must be stashed so only 1008 bytes total instead of 1024, which seems perfectly normal.

Since it works when calling AES_GCM_Decrypt_process with all the data at once followed by AES_GCM_Decrypt_finish, I guess the issue lies in the code that is executed after the processing of a destashed potential tag, which is the case from the second and all subsequent calls to AES_GCM_Decrypt_process.

Khivar commented 5 years ago

Trying to make it work, I added a parameter to AES_GCM_Decrypt_process : containsTag.

ES5 code :

AES_GCM.prototype.AES_GCM_Decrypt_process = function (data) {
    var containsTag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
    var dpos = 0;
    var dlen = data.length || 0;
    var asm = this.aes.asm;
    var heap = this.aes.heap;
    var counter = this.counter;
    var tagSize = this.tagSize;
    var pos = this.aes.pos;
    var len = this.aes.len;
    var rpos = 0;
    if (containsTag) {
      var rlen = len + dlen > tagSize ? (len + dlen - tagSize) & -16 : 0;
    }
    else {
      var rlen = len + dlen > tagSize ? dlen : 0;
    }
    var tlen = len + dlen - rlen;
    var wlen = 0;
    if (((counter - 1) << 4) + len + dlen > _AES_GCM_data_maxLength)
        throw new RangeError('counter overflow');
    var result = new Uint8Array(rlen);
    while (dlen > tlen) {
        wlen = _heap_write(heap, pos + len, data, dpos, dlen - tlen);
        len += wlen;
        dpos += wlen;
        dlen -= wlen;
        wlen = asm.mac(AES_asm.MAC.GCM, AES_asm.HEAP_DATA + pos, wlen);
        wlen = asm.cipher(AES_asm.DEC.CTR, AES_asm.HEAP_DATA + pos, wlen);
        if (wlen)
            result.set(heap.subarray(pos, pos + wlen), rpos);
        counter += wlen >>> 4;
        rpos += wlen;
        pos = 0;
        len = 0;
    }
    if (dlen > 0) {
        len += _heap_write(heap, 0, data, dpos, dlen);
    }
    this.counter = counter;
    this.aes.pos = pos;
    this.aes.len = len;
    return result;
};

I call it by passing false to containsTag and switching it to true when i call it on the last chunk which contains the tag. With that change It now decrypts my files correctly.