DEV Community

Cover image for Secure Text Encryption and Decryption with Vanilla JavaScript
Eddie Gulay
Eddie Gulay

Posted on

Secure Text Encryption and Decryption with Vanilla JavaScript

In today’s digital age, securing sensitive information like API keys, passwords, and user data is more critical than ever. A robust encryption and decryption strategy can prevent unauthorized access and ensure data confidentiality. In this blog post, we’ll explore how to encrypt and decrypt text using vanilla JavaScript, leveraging the Web Crypto API for a modern, secure approach.

Why Use Encryption?

Encryption transforms readable data (plaintext) into a scrambled format (ciphertext) that can only be read if decrypted with the correct key. This ensures that even if someone intercepts the encrypted data, it remains meaningless without the key. A solid encryption mechanism protects:

  • API keys stored in your frontend code.
  • Sensitive user information.
  • Any data transferred over insecure channels.

Let’s dive into how you can implement this securely in JavaScript.


Encryption and Decryption Using AES-GCM

We’ll use AES-GCM (Advanced Encryption Standard - Galois/Counter Mode), a modern standard that provides both encryption and integrity verification. The steps involve:

  1. Password Derivation: Use PBKDF2 (Password-Based Key Derivation Function 2) to derive a secure key from a password.
  2. Salt and IV: Generate a random salt (to make the password derivation unique) and iv (Initialization Vector) for each encryption.
  3. Encryption: Encrypt the plaintext using AES-GCM.
  4. Decryption: Decrypt the ciphertext using the same password and salt/iv.

Code Implementation

Here is the complete JavaScript implementation.

Utilities for Conversion

We’ll convert between ArrayBuffer and hexadecimal for easy data storage and retrieval:

function arrayBufferToHex(buffer) {
    return [...new Uint8Array(buffer)]
        .map(byte => byte.toString(16).padStart(2, '0'))
        .join('');
}

function hexToArrayBuffer(hex) {
    const bytes = new Uint8Array(hex.length / 2);
    for (let i = 0; i < hex.length; i += 2) {
        bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
    }
    return bytes.buffer;
}
Enter fullscreen mode Exit fullscreen mode

Key Derivation from Password

Use PBKDF2 to derive a strong encryption key:

async function getCryptoKey(password) {
    const encoder = new TextEncoder();
    const keyMaterial = encoder.encode(password);
    return crypto.subtle.importKey(
        'raw',
        keyMaterial,
        { name: 'PBKDF2' },
        false,
        ['deriveKey']
    );
}

async function deriveKey(password, salt) {
    const keyMaterial = await getCryptoKey(password);
    return crypto.subtle.deriveKey(
        {
            name: 'PBKDF2',
            salt: salt,
            iterations: 100000,
            hash: 'SHA-256'
        },
        keyMaterial,
        { name: 'AES-GCM', length: 256 },
        false,
        ['encrypt', 'decrypt']
    );
}
Enter fullscreen mode Exit fullscreen mode

Encryption Function

Encrypt text with a password:

async function encryptText(text, password) {
    const encoder = new TextEncoder();
    const salt = crypto.getRandomValues(new Uint8Array(16));
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const key = await deriveKey(password, salt);

    const encrypted = await crypto.subtle.encrypt(
        { name: 'AES-GCM', iv: iv },
        key,
        encoder.encode(text)
    );

    return {
        cipherText: arrayBufferToHex(encrypted),
        iv: arrayBufferToHex(iv),
        salt: arrayBufferToHex(salt)
    };
}
Enter fullscreen mode Exit fullscreen mode

Decryption Function

Decrypt text with the same password:

async function decryptText(encryptedData, password) {
    const { cipherText, iv, salt } = encryptedData;
    const key = await deriveKey(password, hexToArrayBuffer(salt));

    const decrypted = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: hexToArrayBuffer(iv) },
        key,
        hexToArrayBuffer(cipherText)
    );

    const decoder = new TextDecoder();
    return decoder.decode(decrypted);
}
Enter fullscreen mode Exit fullscreen mode

Example Usage

Let’s see how to use these functions:

(async () => {
    const text = 'MySecretAPIKey123456';
    const password = 'StrongPassword123!';

    // Encrypt
    const encryptedData = await encryptText(text, password);
    console.log('Encrypted Data:', encryptedData);

    // Decrypt
    const decryptedText = await decryptText(encryptedData, password);
    console.log('Decrypted Text:', decryptedText);
})();
Enter fullscreen mode Exit fullscreen mode

Security Best Practices

  1. Use a Strong Password: The encryption is only as secure as the password you use.
  2. Store Salt and IV Safely: Always save the salt and iv alongside your encrypted data.
  3. Avoid Hardcoding Secrets: Never hardcode sensitive data or passwords in your codebase.
  4. Use HTTPS: Ensure your application uses HTTPS to protect data in transit.

Encrypting sensitive information like API keys is a fundamental step in securing your applications. I use this for API keys mostly.

Top comments (0)