Skip to content

Instantly share code, notes, and snippets.

@urchymanny
Created December 21, 2023 12:55
Show Gist options
  • Save urchymanny/c8b3c1b30f0083198dfca0066a91e661 to your computer and use it in GitHub Desktop.
Save urchymanny/c8b3c1b30f0083198dfca0066a91e661 to your computer and use it in GitHub Desktop.
An AES-GCM Encryption Utilities for Web Applications
const makeKey = async (key) => {
return await crypto.subtle.importKey(
"raw",
Buffer.from(key, "base64"),
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"]
);
};
export const encryptSymmetric = async (plaintext) => {
const key = process.env.NEXT_PUBLIC_ENCRYPTION_KEY;
const iv = crypto.getRandomValues(new Uint8Array(12));
const encodedPlaintext = new TextEncoder().encode(plaintext);
const secretKey = await makeKey(key);
const ciphertext = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv,
},
secretKey,
encodedPlaintext
);
return {
ciphertext: Buffer.from(ciphertext).toString("base64"),
iv: Buffer.from(iv).toString("base64"),
};
};
export const decryptSymmetric = async (ciphertext, iv) => {
const key = process.env.NEXT_PUBLIC_ENCRYPTION_KEY;
const secretKey = await makeKey(key);
const cleartext = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: Buffer.from(iv, "base64"),
},
secretKey,
Buffer.from(ciphertext, "base64")
);
const data = new TextDecoder().decode(cleartext);
return data;
};
export const saveToLocalStorage = async (name, data) => {
const stringified_data = JSON.stringify(data);
const encrypted_data = await encryptSymmetric(stringified_data);
localStorage.setItem(name, JSON.stringify(encrypted_data));
};
export const getFromLocalStorage = async (name) => {
const raw_data = localStorage.getItem(name);
if (!raw_data) return null;
const encrypted_data = JSON.parse(raw_data);
const decrypted_data = await decryptSymmetric(
encrypted_data.ciphertext,
encrypted_data.iv
);
const un_stringified_data = JSON.parse(decrypted_data);
return un_stringified_data;
};
export const removeFromLocalStorage = (name) => {
localStorage.removeItem(name);
};

AES-GCM Encryption Utilities for Web Applications

This document provides an overview of a series of JavaScript utility functions for handling AES-GCM symmetric encryption and decryption, tailored for web applications. These utilities include functions for key generation, encrypting and decrypting data, and storing or retrieving encrypted data from the browser's local storage.

Functions

1. Key Generation - makeKey

Generates a cryptographic key suitable for AES-GCM encryption and decryption from a base64 encoded string.

const makeKey = async (key) => {
  return await crypto.subtle.importKey(
    "raw",
    Buffer.from(key, "base64"),
    {
      name: "AES-GCM",
      length: 256,
    },
    true,
    ["encrypt", "decrypt"]
  );
};

2. Symmetric Encryption - encryptSymmetric

Encrypts plaintext using the AES-GCM algorithm. Returns an object containing the base64 encoded ciphertext and the initialization vector (IV).

export const encryptSymmetric = async (plaintext) => {
  const key = process.env.NEXT_PUBLIC_ENCRYPTION_KEY;
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encodedPlaintext = new TextEncoder().encode(plaintext);
  const secretKey = await makeKey(key);

  const ciphertext = await crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv,
    },
    secretKey,
    encodedPlaintext
  );

  return {
    ciphertext: Buffer.from(ciphertext).toString("base64"),
    iv: Buffer.from(iv).toString("base64"),
  };
};

3. Symmetric Decryption - decryptSymmetric

Decrypts a base64 encoded ciphertext using the AES-GCM algorithm. Requires the ciphertext and the IV used during encryption.

export const decryptSymmetric = async (ciphertext, iv) => {
  const key = process.env.NEXT_PUBLIC_ENCRYPTION_KEY;
  const secretKey = await makeKey(key);

  const cleartext = await crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv: Buffer.from(iv, "base64"),
    },
    secretKey,
    Buffer.from(ciphertext, "base64")
  );
  const data = new TextDecoder().decode(cleartext);
  return data;
};

4. Save Encrypted Data to Local Storage - saveToLocalStorage

Encrypts and stores data in the browser's local storage. The data is first stringified, then encrypted, and finally stored as a string.

export const saveToLocalStorage = async (name, data) => {
  const stringified_data = JSON.stringify(data);
  const encrypted_data = await encryptSymmetric(stringified_data);
  localStorage.setItem(name, JSON.stringify(encrypted_data));
};

5. Retrieve and Decrypt Data from Local Storage - getFromLocalStorage

Fetches encrypted data from local storage, decrypts it, and parses it back into its original object format.

export const getFromLocalStorage = async (name) => {
  const raw_data = localStorage.getItem(name);
  if (!raw_data) return null;

  const encrypted_data = JSON.parse(raw_data);
  const decrypted_data = await decryptSymmetric(
    encrypted_data.ciphertext,
    encrypted_data.iv
  );
  const un_stringified_data = JSON.parse(decrypted_data);
  return un_stringified_data;
};

6. Remove Data from Local Storage - removeFromLocalStorage

Deletes an item from the browser's local storage by its key.

export const removeFromLocalStorage = (name) => {
  localStorage.removeItem(name);
};

Usage

These utilities are particularly useful in web applications using next or react in general where securing client-side data is crucial. They provide a means to encrypt sensitive data before storing it in the browser and ensure that it can be securely decrypted and used when needed.

To incorporate the encryption utility in a React component for saving data, you can import the saveToLocalStorage function from the encryption module as demonstrated in the following example:

import { saveToLocalStorage } from "./encrypter";

const signUpData = {
  firstName: first.trim(),
  lastName: last.trim(),
};

saveToLocalStorage("nameOfLocalStorageKey", signUpData);

For utilizing the decryption utility in a React component, you can import the getFromLocalStorage function. Here is an example that demonstrates how to fetch and decrypt data stored in local storage:

import React, { useEffect, useState } from "react";
import { getFromLocalStorage } from "./encrypter";

const MyComponent = () => {
  const [clientInfo, setClientInfo] = useState(null);

  useEffect(() => {
    const fetchClientInfo = async () => {
      const info = await getFromLocalStorage("nameOfLocalStorageKey");
      setClientInfo(info);
    };

    fetchClientInfo();
  }, []);

  const { firstName, lastName } = clientInfo || {};

  // Rest of your component logic...
  
  return (
    // Your JSX code...
  );
};

In this example, getFromLocalStorage is used within a useEffect hook to asynchronously fetch and decrypt data from local storage when the component mounts. The decrypted data is then stored in the component's state. This approach ensures that the data is available for use within the component once it has been successfully fetched and decrypted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment