Description
- Version: v8.11.3
- Platform: Darwin foo.local 17.7.0 Darwin Kernel Version 17.7.0: Thu Jun 21 22:53:14 PDT 2018; root:xnu-4570.71.2~1/RELEASE_X86_64 x86_64
- Subsystem: crypto
crypto Decipher docs say that setAuthTag() must be called before final().
In fact setAuthTag must be called before any call to update(), in GCM mode at least.
If you call update() before setAuthTag(), final() will throw regardless of the correctness of the ciphertext and tag.
Test case:
const authTagBeforeUpdate = false; // true works, false throws
const crypto = require('crypto');
const cleartext = Buffer.from("abcdefghijklmnopqrstuvwxyz0123");
const key = Buffer.from("abcdefghijklmnopqrstuvwxyz012345");
const iv = Buffer.from("0123456789ab");
console.log('cleartext', cleartext);
console.log('key ', key);
console.log('iv ', iv);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const ciphertext = Buffer.concat([cipher.update(cleartext), cipher.final()]);
const authTag = cipher.getAuthTag();
console.log('authTag ', authTag);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
if (authTagBeforeUpdate) {
decipher.setAuthTag(authTag);
}
let result_update = decipher.update(ciphertext);
if (!authTagBeforeUpdate) {
decipher.setAuthTag(authTag);
}
let result_final = decipher.final();
let result_cleartext = Buffer.concat([result_update, result_final]);
console.log('result ', result_cleartext);
I suggest changing the API docs to make it clear that the auth tag must be supplied before any data is read in. setAuthTag() must be called before final() and should be called before any call to update().
Second: is this actually the desired behaviour?
As far as I can tell, there is no requirement for GCM to know the auth tag prior to deciphering data. OpenSSL's own example provides it after EVP_DecryptUpdate, before EVP_DecryptFinal_ex (https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Decryption_using_GCM_mode).
In cases where the auth tag is appended to the ciphertext (e.g. Java), as things stand the entire ciphertext must be buffered. For large data this is a serious problem. It would make more sense to decrypt chunks as they are received, treating them carefully and discarding everything if the auth tag eventually proves to be incorrect.