πŸ” Implementing AES-GCM Encryption and Decryption in TypeScript via native crypto

πŸ” Implementing AES-GCM Encryption and Decryption in TypeScript via native crypto
Photo by NASA Hubble Space Telescope / Unsplash

AES-GCM is a popular encryption algorithm used to secure data in transit. Today, I'll show you how to implement AES-GCM encryption and decryption using JavaScript, making it compatible with both browser and Node.js environments.

It's strongly recommended to use authenticated encryption, which includes checks that the ciphertext has not been modified by an attacker. Authentication helps protect against chosen-ciphertext attacks, in which an attacker can ask the system to decrypt arbitrary messages, and use the result to deduce information about the secret key. While it's possible to add authentication to CTR and CBC modes, they do not provide it by default and when implementing it manually one can easily make minor, but serious mistakes. GCM does provide built-in authentication, and for this reason it's often recommended over the other two AES modes.
SubtleCrypto: encrypt() method - Web APIs | MDN
The encrypt() method of the SubtleCrypto interface encrypts data.

Utility Functions

Here are a few utility functions that you can use to encrypt and decrypt data with AES-GCM:

import { Buffer } from 'buffer';

let AES_GCM_KEY: CryptoKey;
let AES_GCM_IV: Uint8Array;
let CRYPTO: any;

const initCryptoKeyIV = async (key: string, iv: string) => {
    CRYPTO = (typeof window !== undefined && (window.crypto?.subtle)) ? window.crypto : await import('crypto').then((crypto) => crypto.webcrypto);
    AES_GCM_KEY = await CRYPTO.subtle.importKey(
        "raw", // format
        Buffer.from(key, "base64"), // key data
        { name: "AES-GCM" }, // algorithm
        true, // extractable
        ["encrypt", "decrypt"] // key usages
    );
    AES_GCM_IV = Buffer.from(iv, "base64"); // iv data
    console.log("errong", key, iv, AES_GCM_KEY, AES_GCM_IV, CRYPTO);
}

export const decrypt = async (encryptedText: string) => {

    const buffer = Buffer.from(encryptedText, "base64");
    const decrypted = await CRYPTO.subtle.decrypt(
        {
            name: "AES-GCM",
            iv: AES_GCM_IV
        },
        AES_GCM_KEY,
        buffer
    );
    return Buffer.from(decrypted).toString();
}

export const encrypt = async (plainText: string) => {

    const buffer = Buffer.from(plainText);
    const decrypted = await CRYPTO.subtle.encrypt(
        {
            name: "AES-GCM",
            iv: AES_GCM_IV
        },
        AES_GCM_KEY,
        buffer
    );
    return Buffer.from(decrypted).toString("base64");
}

Unit Tests

import { initCryptoKeyIV, encrypt, decrypt } from ".";

describe("AES-GCM", () => {
    it("decrypted text should equal plain text", async () => {
        await initCryptoKeyIV("8b/qGo5A82kW3ThhkyN3cyzKo98U54iFk+TKIjN0nfo=", "2K5/8S7fPvrVH0Mk");
        const plain_text = "lengerrong";
        const encrypted_text = await encrypt(plain_text);
        console.log("encrypted_text", encrypted_text);
        const decrypted_text = await decrypt(encrypted_text);
        console.log("decrypted_text", decrypted_text);
        expect(decrypted_text).toEqual(plain_text);
    });
});

Browser and Node.js Compatibility

These functions are compatible with both browser and Node.js environments, allowing you to seamlessly encrypt and decrypt data across different platforms.

Give it a try and let me know what you think! If you have any questions or suggestions, feel free to leave a comment below. Happy coding! πŸ”’βœ¨

Subscribe to Post, Code and Quiet Time.

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe