1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
/*
This file is part of keks-meet (https://codeberg.org/metamuffin/keks-meet)
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2023 metamuffin <metamuffin.org>
*/
import { log } from "../logger.ts";
//! I am not a crypto expert at all! Please read carefully and report any issues to me.
const IV_LENGTH = 12
export async function crypto_seeded_key(seed: string): Promise<CryptoKey> {
log("crypto", "importing seed…")
const seed_key = await window.crypto.subtle.importKey(
"raw",
new TextEncoder().encode(seed),
"PBKDF2",
false,
["deriveKey"]
)
const salt = base64_to_buf("thisisagoodsaltAAAAAAA==") // valid "unique" 16-byte base-64 string
log("crypto", "deriving key…")
const key = await window.crypto.subtle.deriveKey(
{ name: "PBKDF2", salt, iterations: 250000, hash: "SHA-256" },
seed_key,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
)
log("crypto", "ready")
return key
}
export async function crypto_hash(input: string): Promise<string> {
const buf = new TextEncoder().encode("also-a-very-good-salt" + input)
const h = await window.crypto.subtle.digest({ name: "SHA-512" }, buf)
const hex = buf_to_hex(new Uint8Array(h))
return hex
}
export async function crypto_encrypt(key: CryptoKey, data: string): Promise<string> {
const iv = window.crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const ciphertext = new Uint8Array(await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
new TextEncoder().encode(data)
));
const buf = new Uint8Array(iv.byteLength + ciphertext.byteLength);
buf.set(iv, 0);
buf.set(ciphertext, iv.byteLength);
const b64 = buf_to_base64(buf);
return b64;
}
export async function crypt_decrypt(key: CryptoKey, data: string): Promise<string> {
try {
const buf = base64_to_buf(data);
const iv = buf.slice(0, IV_LENGTH);
const ciphertext = buf.slice(IV_LENGTH);
const decryptedContent = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
ciphertext
);
const plain = new TextDecoder().decode(decryptedContent);
return plain
} catch (_e) {
log({ scope: "crypto", warn: true }, "unable to decrypt")
return "{}" // :)
}
}
//* Code that might be useful in the future for signing
// const ECDSA_PARAMS = { name: "ECDSA", namedCurve: "P-521", hash: { name: "SHA-384" } }
// export async function crypto_sign(key: CryptoKey, message: string): Promise<string> {
// const signature = await crypto.subtle.sign(
// ECDSA_PARAMS,
// key,
// new TextEncoder().encode(message)
// )
// return buf_to_base64(new Uint8Array(signature))
// }
// export async function crypto_generate_signing_key(): Promise<CryptoKeyPair> {
// return await crypto.subtle.generateKey(
// ECDSA_PARAMS,
// false,
// ["sign", "verify"]
// )
// }
// export async function crypto_verify(key: CryptoKey, message: string, signature: string): Promise<boolean> {
// return await crypto.subtle.verify(
// ECDSA_PARAMS,
// key,
// base64_to_buf(signature).buffer,
// new TextEncoder().encode(message)
// )
// }
// export async function export_public_signing_key(key: CryptoKey): Promise<string> {
// const buf = await crypto.subtle.exportKey("spki", key)
// return buf_to_base64(new Uint8Array(buf))
// }
// export async function import_public_signing_key(der: string): Promise<CryptoKey | undefined> {
// const bin_der = base64_to_buf(der).buffer; // TODO safety
// return await crypto.subtle.importKey("spki", bin_der, ECDSA_PARAMS, true, ["verify"]) // TODO safety
// }
export function base64_to_buf(data: string): Uint8Array {
const binary_string = globalThis.atob(data);
const bytes = new Uint8Array(binary_string.length);
for (let i = 0; i < binary_string.length; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes;
}
export function buf_to_base64(bytes: Uint8Array): string {
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return globalThis.btoa(binary);
}
export function buf_to_hex(bytes: Uint8Array): string {
return Array.from(bytes).map((b) => b.toString(16).padStart(2, '0')).join('');
}
|