1
0

AES convenience functions for encryption

This commit is contained in:
Zef Hemel 2023-07-14 11:23:17 +02:00
parent 79a1120198
commit 839a11bfd7
2 changed files with 144 additions and 0 deletions

33
common/crypto/aes.test.ts Normal file
View File

@ -0,0 +1,33 @@
import { assertEquals } from "../../test_deps.ts";
import {
decryptAES,
decryptPath,
deriveKeyFromPassword,
encryptAES,
encryptPath,
} from "./aes.ts";
Deno.test("AES encryption and decryption", async () => {
const password = "YourPassword";
const salt = "UniquePerUserSalt";
const message = "Hello, World!";
const key = await deriveKeyFromPassword(password, salt);
const encrypted = await encryptAES(key, message);
const decrypted = await decryptAES(key, encrypted);
assertEquals(decrypted, message);
// Test that checks if a path is encrypted the same way every time and can be unencrypted
const path =
"this/is/a/long/path/that/needs/to/be/encrypted because that's what we do.md";
const encryptedPath = await encryptPath(key, path);
const encryptedPath2 = await encryptPath(key, path);
// Assure two runs give the same result
assertEquals(encryptedPath, encryptedPath2);
// Ensure decryption works
const decryptedPath = await decryptPath(key, encryptedPath);
console.log(encryptedPath);
assertEquals(decryptedPath, path);
});

111
common/crypto/aes.ts Normal file
View File

@ -0,0 +1,111 @@
import {
base64Decode,
base64Encode,
} from "../../plugos/asset_bundle/base64.ts";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
export async function deriveKeyFromPassword(
password: string,
salt: string,
): Promise<CryptoKey> {
const baseKey = encoder.encode(password);
const importedKey = await window.crypto.subtle.importKey(
"raw",
baseKey,
{ name: "PBKDF2" },
false,
["deriveKey"],
);
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: encoder.encode(salt),
iterations: 10000,
hash: "SHA-256",
},
importedKey,
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"],
);
}
export async function encryptAES(
key: CryptoKey,
message: string,
): Promise<ArrayBuffer> {
const iv = crypto.getRandomValues(new Uint8Array(12));
const encodedMessage = encoder.encode(message);
const ciphertext = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv,
},
key,
encodedMessage,
);
return appendBuffer(iv, ciphertext);
}
export async function decryptAES(
key: CryptoKey,
data: ArrayBuffer,
): Promise<string> {
const iv = data.slice(0, 12);
const ciphertext = data.slice(12);
const decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv,
},
key,
ciphertext,
);
return decoder.decode(decrypted);
}
function appendBuffer(buffer1: ArrayBuffer, buffer2: ArrayBuffer): ArrayBuffer {
const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
// This is against security recommendations, but we need a way to always generate the same encrypted path for the same path and password
const pathIv = new Uint8Array(12); // 12 bytes of 0
export async function encryptPath(
key: CryptoKey,
path: string,
): Promise<string> {
const encodedMessage = encoder.encode(path);
const ciphertext = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: pathIv,
},
key,
encodedMessage,
);
return base64Encode(new Uint8Array(ciphertext));
}
export async function decryptPath(
key: CryptoKey,
data: string,
): Promise<string> {
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: pathIv,
},
key,
base64Decode(data),
);
return decoder.decode(decrypted);
}