AES convenience functions for encryption
This commit is contained in:
parent
79a1120198
commit
839a11bfd7
33
common/crypto/aes.test.ts
Normal file
33
common/crypto/aes.test.ts
Normal 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
111
common/crypto/aes.ts
Normal 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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user