Encrypt / Decrypt with AES (CCM & GCM) in Node.JS (Part 2 – Concatenate the Authentication Tag)

In the past I wrote a post about encrypting and decrypting text strings with AES in GCM and CCM mode. At the time, the demo was written to show how the algorithm works in those modes.

Today I needed to handle the inclusion of such Authentication Tag inside the encrypted string so that when decrypting the message I would find it inside itself rather than asking the user for it.

In short, it’s a simple concatenation at the end of the encrypted message. When decrypting, I know that the last 16 bytes belong to the Authentication Tag.

Here is the code:

var crypto = require("crypto");

var key128 = crypto.randomBytes(128 / 8); // 16 bytes
var key192 = crypto.randomBytes(192 / 8); // 24 bytes
var key256 = crypto.randomBytes(256 / 8); // 32 bytes

var iv = "Hook'em Horns"; // The length of the initialization vector (iv) N must be between 7 and 13 bytes (7 ≤ N ≤ 13).
var plainText = "Texas Longhorns"; // The length of the plaintext is limited to 2 ** (8 * (15 - N)) bytes. (Min: 65536 bytes when IV length is 13 bytes, Max: 18446744073709551616 bytes when IV length is 7 bytes)

console.log("\n >>> AES CCM mode demo >>>");

runAESCCMDemo("aes-128-ccm", key128);
runAESCCMDemo("aes-192-ccm", key192);
runAESCCMDemo("aes-256-ccm", key256);
runAESCCMDemo("id-aes128-CCM", key128);
runAESCCMDemo("id-aes192-CCM", key192);
runAESCCMDemo("id-aes256-CCM", key256);

console.log("\n >>> AES GCM mode demo >>>");

runAESGCMDemo("aes-128-gcm", key128);
runAESGCMDemo("aes-192-gcm", key192);
runAESGCMDemo("aes-256-gcm", key256);
runAESGCMDemo("id-aes128-GCM", key128);
runAESGCMDemo("id-aes192-GCM", key192);
runAESGCMDemo("id-aes256-GCM", key256);

function runAESCCMDemo(algorithm, key) {

    try {

        // The options argument controls stream behavior and is optional except when a cipher in CCM or OCB mode is used (e.g. 'aes-128-ccm').
        // The authentication tag length must be specified during cipher creation by setting the authTagLength option and must be one of 4, 6, 8, 10, 12, 14 or 16 bytes.

        var cipher = crypto.createCipheriv(algorithm, key, iv, {
            authTagLength: 16
        });

        var decipher = crypto.createDecipheriv(algorithm, key, iv, {
            authTagLength: 16
        });

        console.log("\n" + algorithm + ':');

        // Encrypting
        var encText = (Buffer.concat([cipher.update(plainText), cipher.final(), cipher.getAuthTag()])).toString("hex"); // Alternatively, base64 also works
        console.log("E: " + encText);

        // The process for Decryptnig a String starts here
        var encRawTextBuff = Buffer.from(encText, "hex"); // Alternatively, base64 also works

        const authTagBuff = encRawTextBuff.subarray(encRawTextBuff.length - 16); // Returns a new Buffer that references the same memory as the original, but offset and cropped by the start and end indices.
        const encTextBuff = encRawTextBuff.subarray(0, encRawTextBuff.length - 16); // Returns a new Buffer that references the same memory as the original, but offset and cropped by the start and end indices.

        // console.log(">>>> Auth Tag: " + authTagBuff.toString("hex"));
        // console.log(">>>> Encryoted Text: " + encTextBuff.toString("hex"));

        decipher.setAuthTag(authTagBuff);

        // Decrypting
        var decText = decipher.update(encTextBuff);
        decText += decipher.final('utf8');
        console.log("D: " + decText);
        console.log("MATCH: " + (decText == plainText));

    } catch (e) {

        console.log(e);

    }

}

function runAESGCMDemo(algorithm, key) {

    try {

        // The options argument controls stream behavior and is optional except when a cipher in CCM or OCB mode is used (e.g. 'aes-128-ccm').
        // In GCM mode, the authTagLength option is not required but can be used to set the length of the authentication tag that will be returned by getAuthTag() and defaults to 16 bytes.

        var cipher = crypto.createCipheriv(algorithm, key, iv, {
            authTagLength: 16
        }); // In GCM mode authTagLength is not required but I do it to make sure it's always 16 bytes
        var decipher = crypto.createDecipheriv(algorithm, key, iv, {
            authTagLength: 16
        }); // In GCM mode authTagLength is not required but I do it to make sure it's always 16 bytes

        console.log("\n" + algorithm + ':');

        // Encrypting
        var encText = (Buffer.concat([cipher.update(plainText), cipher.final(), cipher.getAuthTag()])).toString("hex"); // Alternatively, base64 also works
        console.log("E: " + encText);

        // The process for Decryptnig a String starts here
        var encRawTextBuff = Buffer.from(encText, "hex"); // Alternatively, base64 also works

        const authTagBuff = encRawTextBuff.subarray(encRawTextBuff.length - 16); // Returns a new Buffer that references the same memory as the original, but offset and cropped by the start and end indices.
        const encTextBuff = encRawTextBuff.subarray(0, encRawTextBuff.length - 16); // Returns a new Buffer that references the same memory as the original, but offset and cropped by the start and end indices.

        // console.log(">>>> Auth Tag: " + authTagBuff.toString("hex"));
        // console.log(">>>> Encryoted Text: " + encTextBuff.toString("hex"));

        decipher.setAuthTag(authTagBuff);

        // Decrypting
        var decText = decipher.update(encTextBuff);
        decText += decipher.final('utf8');
        console.log("D: " + decText);
        console.log("MATCH: " + (decText == plainText));

    } catch (e) {

        console.log(e);

    }

}

Your output should be similar to this:

 >>> AES CCM mode demo >>>

aes-128-ccm:
E: b171becdef6e78805d65246e1701a082493da4c43a2e120739dffb38ab3522
D: Texas Longhorns
MATCH: true

aes-192-ccm:
E: 2afc341f3814c2f916639af99a116b942cebd4dcf35428d294a7a8e536b74a
D: Texas Longhorns
MATCH: true

aes-256-ccm:
E: 29f448a3b010adcfd3de58c1e34cd6386c57c9ab5be7415c0b955527808d10
D: Texas Longhorns
MATCH: true

id-aes128-CCM:
E: b171becdef6e78805d65246e1701a082493da4c43a2e120739dffb38ab3522
D: Texas Longhorns
MATCH: true

id-aes192-CCM:
E: 2afc341f3814c2f916639af99a116b942cebd4dcf35428d294a7a8e536b74a
D: Texas Longhorns
MATCH: true

id-aes256-CCM:
E: 29f448a3b010adcfd3de58c1e34cd6386c57c9ab5be7415c0b955527808d10
D: Texas Longhorns
MATCH: true

 >>> AES GCM mode demo >>>

aes-128-gcm:
E: 89e5381113a0bdd4159d001c3f69cf6845254993d857d9b0b1fb563f965983
D: Texas Longhorns
MATCH: true

aes-192-gcm:
E: 80e642b12df9865d136b2735e459fc64064c8b419bd63778ccb6b7a96e5cf0
D: Texas Longhorns
MATCH: true

aes-256-gcm:
E: 936cf62f521c3a586414fba207dd8ae7b48d35624c5f38f7669af7bb0e69a1
D: Texas Longhorns
MATCH: true

id-aes128-GCM:
E: 89e5381113a0bdd4159d001c3f69cf6845254993d857d9b0b1fb563f965983
D: Texas Longhorns
MATCH: true

id-aes192-GCM:
E: 80e642b12df9865d136b2735e459fc64064c8b419bd63778ccb6b7a96e5cf0
D: Texas Longhorns
MATCH: true

id-aes256-GCM:
E: 936cf62f521c3a586414fba207dd8ae7b48d35624c5f38f7669af7bb0e69a1
D: Texas Longhorns
MATCH: true

Sources:
https://nodejs.org/api/buffer.html#buffer_buf_subarray_start_end