Skip to content

Instantly share code, notes, and snippets.

@FND
Last active December 20, 2021 13:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FND/06f761d11425a220cc7ab8188e102eaf to your computer and use it in GitHub Desktop.
Save FND/06f761d11425a220cc7ab8188e102eaf to your computer and use it in GitHub Desktop.
encryption/decryption
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Encryption/Decryption</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
max-width: 60ch;
margin: 1rem auto;
font-family: sans-serif;
}
form > * {
margin-top: 0;
margin-bottom: 0;
}
form > * + * {
margin-top: 0.5rem;
}
textarea {
display: block;
width: 100%;
height: 5rem;
}
label,
output {
display: block;
}
output[name=encrypted] {
word-break: break-all;
}
output[name=decrypted] {
white-space: pre;
}
</style>
</head>
<body>
<h1>Encryption/Decryption</h1>
<p>
adapted from vjeux's
<a href="https://blog.excalidraw.com/end-to-end-encryption/">Excalidraw write-up</a>
</p>
<form>
<textarea>lörem ipsüm dolor ßit ämet</textarea>
<button>check</button>
<label>
<b>password:</b>
<input value="s33kr1t">
</label>
<label>
<b>encrypted:</b>
<output name="encrypted"></output>
</label>
<label>
<b>decrypted:</b>
<output name="decrypted"></output>
</label>
</form>
<script>
(function() {
let SCRYPT = window.crypto.subtle;
let ALGO = "AES-GCM";
let SEP = "|";
let SALT = str2bytes("a67190bb-e9f4-4c3f-b690-392414a46d3b").buffer;
document.querySelector("form").addEventListener("submit", onSubmit);
async function onSubmit(ev) {
ev.preventDefault();
let content = this.querySelector("textarea").value;
let password = this.querySelector("input").value;
let encrypted = await encrypt(content, password);
Object.entries({
encrypted: encrypted,
decrypted: await decrypt(encrypted, password)
}).forEach(([name, value]) => {
this.querySelector(`[name=${name}]`).textContent = value;
});
}
async function encrypt(content, password) {
let iv = window.crypto.getRandomValues(new Uint8Array(16));
let encrypted = await SCRYPT.encrypt({ name: ALGO, iv },
await deriveKey(password), str2bytes(content));
return [iv, new Uint8Array(encrypted)]. // XXX: conversion valid?
map(block => btoa(block.join(","))).
join(SEP);
}
async function decrypt(content, password) {
let [iv, encrypted] = content.split(SEP).
map(block => Uint8Array.from(atob(block).split(","), str2int));
let decrypted = await SCRYPT.decrypt({ name: ALGO, iv },
await deriveKey(password), encrypted);
return new window.TextDecoder().decode(new Uint8Array(decrypted));
}
async function deriveKey(password) {
let secret = await SCRYPT.importKey("raw", str2bytes(password), "PBKDF2",
false, ["deriveBits", "deriveKey"]);
return await SCRYPT.deriveKey({
name: "PBKDF2",
salt: SALT,
iterations: 100000,
hash: "SHA-256"
}, secret, { name: ALGO, length: 256 }, true, ["encrypt", "decrypt"]);
}
function str2bytes(v) {
return new TextEncoder().encode(v);
}
function str2int(v) {
return parseInt(v, 10);
}
}());
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment