# How to store private keys securely in local storage? **Published by:** [plusminushalf.(eth/lens) 🦇🔊](https://paragraph.com/@plusminushalf/) **Published on:** 2022-11-18 **URL:** https://paragraph.com/@plusminushalf/how-to-store-private-keys-securely-in-local-storage ## Content I recently started working on a chrome-extension wallet compatible with 4337, you can read more about it here. One of the challenges in creating a wallet is how to store private keys securely in the local storage of the browser. In this blog, I will be exploring how Metamask & Tally-ho wallet stores their private keys. I hope you and I both will learn during this exploration. If we want the data to be encrypted with a password & then stored, the best standard to use is AES-FCM. The crypto API provided by the browsers support this by default. To encrypt a message using AES-FCM we need two things, a key which will be used to encrypt the messages and an initializationVector to add randomness.How to create a key?We can use crypto API importkey to generate our master key and then derive the key to encrypt messages for AES-GCM from the master key using deriveKey function. While creating the key we will also generate random bytes salt, the salt must be saved and is needed along with the password to recover the key. Below is the code which generates a new Key if the salt is not passed & recovers the key if the salt is passed.type SaltedKey = { salt: string key: CryptoKey } async function generateSalt(): Promise<string> { const saltBuffer = crypto.getRandomValues(new Uint8Array(64)) return bufferToBase64(saltBuffer) } async function generateOrRecoverKey( password: string, existingSalt?: string ): Promise<SaltedKey> { const { crypto } = global; const salt = existingSalt || (await generateSalt()) const encoder = new TextEncoder(); const derivationKey = await crypto.subtle.importKey( "raw", encoder.encode(password), { name: "PBKDF2" }, false, ["deriveKey"] ) const key = await crypto.subtle.deriveKey( { name: "PBKDF2", salt: encoder.encode(salt), iterations: 1000000, hash: "SHA-256", }, derivationKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ) return { key, salt, } } How to create an initialization vector?To create our initializationVector we can use the crypto API provided by the browsers.const initializationVector = crypto.getRandomValues(new Uint8Array(16)) Encrypting textNow we have generated the key & also initializationVector. We are ready to encrypt our messages. Before encrypting our message, we must encode it using TextEncoder. It takes in the string & emits a stream of UTF-8 bytes.type EncryptedVault = { salt: string initializationVector: string cipherText: string } async function encryptMessage( message: string, password: string ): Promise<Vault> { const encoder = new TextEncoder() const encodedPlaintext = encoder.encode(message) const { key, salt } = await generateOrRecoverKey(password) const initializationVector = crypto.getRandomValues( new Uint8Array(16) ) const cipherText = await crypto.subtle.encrypt( { name: "AES-GCM", iv: initializationVector }, key, encodedPlaintext ) return { salt, initializationVector: bufferToBase64(initializationVector), cipherText: bufferToBase64(cipherText), } } When we call the above function, encryptMessage, we get three things in return salt, initializationVector, cipherText. We can store all these three values in the browser's localStorage. These values along with the user's password can be used to decrypt the cipherText.How to decrypt cipher text?To decrypt the cipherText, we need the three values we stored above along with the user's password. We will first recover the encryption key using the password & salt. After that, we will use the key & initializationVector to decrypt the cipherText. The code below can be used to decrypttype EncryptedVault = { salt: string initializationVector: string cipherText: string } async function decryptCipherText( vault: EncryptedVault, password: string ): Promise<V> { const { crypto } = global const { initializationVector, salt, cipherText } = vault const { key } = await generateOrRecoverKey(password, salt) const plaintext = await crypto.subtle.decrypt( { name: "AES-GCM", iv: base64ToBuffer(initializationVector) }, key, base64ToBuffer(cipherText) ) return new TextDecoder().decode(plaintext) } UsageLet's round up with the usage of functions we have created above to encyrpt & decrypt a message.// Encrypting message const password = "<Some-Strong-User-Password>" const messageToEncrypt = "Hello, world" // We can save this vault directly in localstorage & retrieve later when we need to decrypt the stored message const vault = encryptMessage(messageToEncrypt, password) // Decrypting message const descryptedMessage = decryptCipherText(vault, password); // Validate the messages are equal :) assert(descryptedMessage === messageToEncrypt) ReferenceMetamask has also released a module which will do all these stuff for you & give you simpler API, check out their module browser-passworder Usage:const { strict: assert } = require('assert'); const passworder = require('browser-passworder'); const secrets = { coolStuff: 'all', ssn: 'livin large' }; const password = 'hunter55'; passworder .encrypt(password, secrets) .then(function (blob) { return passworder.decrypt(password, blob); }) .then(function (result) { assert.deepEqual(result, secrets); }); Imported from my previous compromised blog. ## Publication Information - [plusminushalf.(eth/lens) 🦇🔊](https://paragraph.com/@plusminushalf/): Publication homepage - [All Posts](https://paragraph.com/@plusminushalf/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@plusminushalf): Subscribe to updates - [Twitter](https://twitter.com/plusminushalf): Follow on Twitter