node.jsencryptionaes-gcm

"Unsupported state or unable to authenticate data" with aes-128-gcm in Node


I'm trying to implement encrypt/decrypt functions using aes-128-gcm as provided by node crypto. From my understanding, gcm encrypts the ciphertext but also hashes it and provides this as an 'authentication tag'. However, I keep getting the error: "Unsupported state or unable to authenticate data".

I'm not sure if this is an error in my code - looking at the encrypted ciphertext and auth tag, the one being fetched by the decrypt function is the same as the one produced by the encrypt function.

    function encrypt(plaintext) {
    // IV is being generated for each encryption
    var iv = crypto.randomBytes(12),
        cipher = crypto.createCipheriv(aes,key,iv),
        encryptedData = cipher.update(plaintext),
        tag;

    // Cipher.final has been called, so no more encryption/updates can take place
    encryptedData += cipher.final();

    // Auth tag must be generated after cipher.final()
    tag = cipher.getAuthTag();

    return encryptedData + "$$" + tag.toString('hex') + "$$" + iv.toString('hex');
}

function decrypt(ciphertext) {
    var cipherSplit = ciphertext.split("$$"),
        text = cipherSplit[0],
        tag = Buffer.from(cipherSplit[1], 'hex'),
        iv = Buffer.from(cipherSplit[2], 'hex'),
        decipher = crypto.createDecipheriv(aes,key,iv);

    decipher.setAuthTag(tag);

    var decryptedData = decipher.update(text);

    decryptedData += decipher.final();
}

The error is being thrown by decipher.final().


Solution

  • I managed to fix this: the issue was that I wasn't specifying an encoding type for cipher.final() and I was returning it within a String, so it wasn't returning a Buffer object, which decipher.final() was expecting.

    To fix, I add 'utf-8' to 'hex' encoding parameters within my cipher.update and cipher.final, and vice versa in decipher.

    Edited to add code example - note this is from 2018, so may be outdated now.

    function encrypt(plaintext) {
        // IV is being generated for each encryption
        var iv = crypto.randomBytes(12),
            cipher = crypto.createCipheriv(aes,key,iv),
             encryptedData = cipher.update(plaintext, 'utf-8', 'hex'),
            tag;
    
        // Cipher.final has been called, so no more encryption/updates can take place
         encryptedData += cipher.final('hex');
    
        // Auth tag must be generated after cipher.final()
        tag = cipher.getAuthTag();
    
        return encryptedData + "$$" + tag.toString('hex') + "$$" + iv.toString('hex');
    }
    
    function decrypt(ciphertext) {
        var cipherSplit = ciphertext.split("$$"),
            text = cipherSplit[0],
             tag = Buffer.from(cipherSplit[1], 'hex'),
                iv = Buffer.from(cipherSplit[2], 'hex'),
                decipher = crypto.createDecipheriv(aes, key, iv);
    
    
        decipher.setAuthTag(tag);
    
        var decryptedData = decipher.update(text, 'hex', 'utf-8');
    
       decryptedData += decipher.final('utf-8');
    }