Private key (AES-CBC) encryption and decryption in Deno

📜

I found myself needing to encrypt a small string on Deno (and subsequently decrypt it later) for a small side project. I checked out a few websites with code samples. But those didn’t work/were out of date. So this is what worked for me:

Note: I am not a security expert and I don’t play one on TV. You shouldn’t trust anything important with random code you found on the internet. Seriously.

1. Save the key

First we generate a key using the standard SubtleCrypto api that ships with Deno. Then export a JSON webkey and save it. I’m putting it in an environmental variable. Make sure it is safe and secure.


const key = await crypto.subtle.generateKey({ name: "AES-CBC", length: 128 },true,["encrypt", "decrypt"]);
const rawKey = JSON.stringify(await crypto.subtle.exportKey("jwk", key));

2. Encrypt a string

Here we take the JSON webkey from our environmental variable that we created in step one and import it. Then using the key along with some random bytes, the initialization vector iv, we encrypt the string. The last two lines deal with getting the encrypted string into a format to save. The iv needs to be random and unique per message but it doesn’t need to be secret. So we can prepend it to the encrypted message.

You could potentially take this one step further and get a hex string from the encrypted bytes and save that. But for my needs I stopped here and stored the comma separated string of numbers.


async function encryptMe(decrypted)
{
    const _appSecret = JSON.parse(Deno.env.get("APP_SECRET"));
    const iv = await crypto.getRandomValues(new Uint8Array(16)); 
    const key = await crypto.subtle.importKey("jwk", _appSecret, "AES-CBC", true, ["encrypt", "decrypt"]);
    const encrypted = await crypto.subtle.encrypt({name: "AES-CBC", iv}, key, new TextEncoder().encode(decrypted));
    const encryptedBytes = new Uint8Array(encrypted);
    return `${iv.toString()}|${encryptedBytes.toString()}`;
}

3. Decrypt a string

The next step is to decrypt a string we previously encrypted. After we make a key we will need to separate out the two parts of the passed in message. The first part specifies the iv that was used and the second stores the encrypted message. You can use the Uint8Array.from to re-create the Unit8Array. Then call the decrypt method from the key, use a text decoder and you have your decrypted message. Yay! 👏


async function decryptMe(encrypted) 
{
    const _appSecret = JSON.parse(Deno.env.get("APP_SECRET"));
    const key = await crypto.subtle.importKey("jwk", _appSecret, "AES-CBC", true, ["encrypt", "decrypt"]);
    const ivPart = encrypted.split('|')[0];
    const encryptedPart = encrypted.split('|')[1];

    const encryptedBytes = Uint8Array.from(encryptedPart.split(',').map(num => parseInt(num)));
    const iv = Uint8Array.from(ivPart.split(',').map(num => parseInt(num)));
    
    const decrypted = await crypto.subtle.decrypt({name: "AES-CBC", iv}, key, encryptedBytes);
    const decryptedBytes = new Uint8Array(decrypted);
    return new TextDecoder().decode(decryptedBytes);
}

Permission noticeTHE CODE SAMPLE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE CODE SAMPLE OR THE USE OR OTHER DEALINGS WITH THE CODE SAMPLE.

Join the conversation!

Send along a webmention

Grant a simple like