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