Private key (AES) encryption & decryption in Deno
Introduction
Encryption and decryption allow two communicating parties to disguise information they send to each other. The sender encrypts, or scrambles, information before sending it. The receiver decrypts, or unscrambles, the information after receiving it. While in transit, the encrypted information is unintelligible to an intruder.
Symmetric Key Cryptography is when a secret key is leveraged for both encryption and decryption functions. This method is the opposite of Asymmetric Encryption, where one key is used to encrypt and another is used to decrypt. During this process, data is converted to a format that cannot be read or inspected by anyone who does not have the secret key that was used to encrypt it.
From Wiki:
The Advanced Encryption Standard (AES), also known by its original name Rijndael is a specification for the encryption of electronic data established by the U.S. National Institute of Standards and Technology (NIST) in 2001. AES became effective as a U.S. federal government standard on May 26, 2002, after approval by the U.S. Secretary of Commerce.
Since the last few releases, Deno has been continually building support for web standard crypto APIs. Deno (as of v1.15) now has support for both RSA (asymmetric) and AES (symmetric) cryptosystems.
We’ve already seen how to perform public key cryptography in an earlier article.
In this article, we’ll learn how to generate/import key, encrypt data, and decrypt data using AES-CBC algorithm. There are other variants like AES-GCM, but the basic usage is the same regardless of the type of algorithm.
AES cryptography
Any symmetric cryptosystem has three standard parts in the process:
- Generate/Import key: Generates/imports a key that can be used for encryption and decryption
- Encryption: Encrypts data using key
- Decryption: Decrypts data using key
Let’s see all the steps in detail along with examples.
Generate key
The key used for encryption & decryption can either be generated or imported. In production grade distributed systems, the key would usually be generated in a different place and gets imported during the application startup. Let’s go over both for completeness.
The web standard crypto.subtle.generateKey() function can be used to generate an AES key. The function takes the following inputs:
- name: Name of the algorithm (should be AES-CBC)
- length: Length of the key (usually 128)
- exportable: true if key is exportable
- Uses: The uses of the key (usually encrypt & decrypt)
const key = await crypto.subtle.generateKey(
{ name: "AES-CBC", length: 128 },
true,
["encrypt", "decrypt"],
);//key: CryptoKey {
type: "secret",
extractable: true,
algorithm: { name: "AES-CBC", length: 128 },
usages: [ "encrypt", "decrypt" ]
}
The web standard crypto.subtle.exportKey() function can be used to export key in the raw format (or JWK, etc.). The inputs are:
- format: The format of the key
- Key: The key in CryptoKey format
const rawKey = new Uint8Array(await crypto.subtle.exportKey("raw", key));//rawKey: Uint8Array(16) [238,17,126,165,51,88,22,218,53,172,137,113,210,198,109,117]
As mentioned earlier, importing an AES key would be a more common case for distributed applications. The web standard crypto.subtle.importKey() function can be used to import a raw (or JWK, etc.) key into CryptoKey format. The importKey() function takes the same inputs as generateKey() with an additional input: the raw key.
const rawKey=new Uint8Array([238,17,126,165,51,88,22,218,53,172,137,113,210,198,109,117]);
const key = await crypto.subtle.importKey(
"raw",
rawKey.buffer,
"AES-CBC",
true,
["encrypt", "decrypt"],
);//key: CryptoKey {
type: "secret",
extractable: false,
algorithm: { name: "AES-CTR", length: 128 },
usages: [ "encrypt", "decrypt" ]
}
Encryption
To encrypt data, crypto.subtle.encrypt() function need to be used. The inputs are:
- Name of the algorithm (AES-CBC)
- Random initialization vector
- Key
- Data to encrypt (data must be ArrayBuffer or typed array)
The output is an ArrayBuffer containing encrypted data that can be viewed using Uint8Array.
const plainText="Learning Deno is fun!!";
const iv = await crypto.getRandomValues(new Uint8Array(16));
const encrypted = await crypto.subtle.encrypt(
{name: "AES-CBC", iv},
key,
new TextEncoder().encode(plainText),
);
const encryptedBytes=new Uint8Array(encrypted);//encryptedBytes: Uint8Array(32) [242,154,162,249,71,99,143,64,225,177,253,7,64,43,246,149,202,212,157,85,110,70,81,116,59,185,62,3,190,77,9,20]
If needed, the encrypted bytes can also be converted to hex string:
import {encode as he} from "https://deno.land/std/encoding/hex.ts";const te=(s:string)=>new TextEncoder().encode(s),
td=(d:Uint8Array)=>new TextDecoder().decode(d);const plainText="Learning Deno is fun!!";
const iv = await crypto.getRandomValues(new Uint8Array(16));
const encrypted = await crypto.subtle.encrypt(
{name: "AES-CBC", iv},
key,
te(plainText),
);
const encryptedBytes=new Uint8Array(encrypted);
const hexBytes=td(he(encryptedBytes));//hexBytes: fbff6f777401853c042b2c53aefe3142b6f41c172d4e332805b7e49c6a26434d
Decryption
To decrypt data, crypto.subtle.decrypt() function need to be used. The inputs are:
- Name of the algorithm (AES-CBC)
- Initialization vector (same as used for encryption)
- Key
- Data to decrypt (data must be ArrayBuffer or typed array)
The output is an ArrayBuffer containing decrypted data that can be viewed using Uint8Array. The decrypted data can be converted to string using TextDecoder.
const td=(d:Uint8Array)=>new TextDecoder().decode(d);const decrypted = await crypto.subtle.decrypt(
{name: "AES-CBC", iv},
key,
encryptedBytes,
);
const decryptedBytes=new Uint8Array(decrypted);
const decryptedText=td(decryptedBytes);//decryptedText: Learning Deno is fun!!
If the input data was hex encoded, it requires to be decoded before decrypting:
import {decode as hd} from "https://deno.land/std/encoding/hex.ts";const te=(s:string)=>new TextEncoder().encode(s),
td=(d:Uint8Array)=>new TextDecoder().decode(d);const hexBytes="a5e697d53fe61c7896517177561d46772c1a9eb026f658e61931cdd78e82b788";const decrypted = await crypto.subtle.decrypt(
{name: "AES-CBC", iv},
key,
hd(te(hexBytes)),
);
const decryptedBytes=new Uint8Array(decrypted);
const decryptedText=td(decryptedBytes);//decryptedText: Learning Deno is fun!!
That’s all about AES encryption & decryption in Deno. Here is the complete code: