Skip to content

Instantly share code, notes, and snippets.

@Mix-Liten
Created August 24, 2023 10:31
Show Gist options
  • Save Mix-Liten/0ea954b3cea402ce5a4c883973b20229 to your computer and use it in GitHub Desktop.
Save Mix-Liten/0ea954b3cea402ce5a4c883973b20229 to your computer and use it in GitHub Desktop.
Symmetric Encryption in Web environment
class SymmetricEncryptor {
#key
/**
* Create a new SymmetricEncryptor instance.
* @param {string} key - The base64-encoded encryption key.
*/
constructor(key) {
/**
* The encryption key.
* @type {Uint8Array}
* @private
*/
this.#key = new Uint8Array(
atob(key)
.split('')
.map(char => char.charCodeAt(0))
)
}
/**
* Encrypts the given plaintext using AES-GCM.
* @param {string} plaintext - The plaintext message to encrypt.
* @returns {Promise<{ciphertext: string, iv: string}>} A Promise that resolves to an object containing the ciphertext and initialization vector (IV).
*/
async encrypt(plaintext) {
const iv = crypto.getRandomValues(new Uint8Array(12))
const encodedPlaintext = new TextEncoder().encode(plaintext)
const secretKey = await crypto.subtle.importKey(
'raw',
this.#key,
{
name: 'AES-GCM',
length: 256,
},
true,
['encrypt', 'decrypt']
)
const ciphertext = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
},
secretKey,
encodedPlaintext
)
return {
ciphertext: btoa(String.fromCharCode(...new Uint8Array(ciphertext))),
iv: btoa(String.fromCharCode(...iv)),
}
}
/**
* Decrypts the given ciphertext using AES-GCM.
* @param {string} ciphertext - The ciphertext to decrypt.
* @param {string} iv - The initialization vector (IV) used during encryption.
* @returns {Promise<string>} A Promise that resolves to the decrypted plaintext message.
*/
async decrypt(ciphertext, iv) {
const secretKey = await crypto.subtle.importKey(
'raw',
this.#key,
{
name: 'AES-GCM',
length: 256,
},
true,
['encrypt', 'decrypt']
)
const cleartext = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv: new Uint8Array(
atob(iv)
.split('')
.map(char => char.charCodeAt(0))
),
},
secretKey,
new Uint8Array(
atob(ciphertext)
.split('')
.map(char => char.charCodeAt(0))
)
)
return new TextDecoder().decode(cleartext)
}
}
/**
* Generates a random AES-GCM encryption key and returns it as a base64-encoded string.
* @returns {Promise<string>} A Promise that resolves to the generated base64-encoded encryption key.
*/
async function generateKey() {
/**
* Generate a random AES-GCM key pair.
* @type {CryptoKeyPair}
*/
const key = await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256,
},
true,
['encrypt', 'decrypt']
)
const keyBuffer = await crypto.subtle.exportKey('raw', key)
const keyArray = new Uint8Array(keyBuffer)
const base64Key = btoa(String.fromCharCode(...keyArray))
return base64Key
}
// Example usage
const plaintext = '----- Secret Data -----'
const key = await generateKey()
const symmetricEncryptor = new SymmetricEncryptor(key)
console.log('Init sample:', { plaintext, key })
const { ciphertext, iv } = await symmetricEncryptor.encrypt(plaintext)
console.log('Encrypted return:', { ciphertext, iv })
const decryptedPlaintext = await symmetricEncryptor.decrypt(ciphertext, iv)
console.log('Decrypted Plaintext:', decryptedPlaintext)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment