-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaes.js
More file actions
89 lines (73 loc) · 3.27 KB
/
aes.js
File metadata and controls
89 lines (73 loc) · 3.27 KB
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
import { concatWithLength, splitWithLength } from './util.js';
import { encode as unicodeEncode, decode as unicodeDecode } from './unicode.js';
import { uencode as ubase64Encode, udecode as ubase64Decode } from './base64.js';
import { hash as argon2Hash, generateSalt as argon2Salt } from './argon2.js';
import { random as getRandom } from './csprng.js';
export async function encrypt(data, password, iv = null, salt = null, throw_on_empty = true) {
if (window.speedups_loaded instanceof Promise) {
await window.speedups_loaded;
}
iv = iv ? iv : getRandom(12, Uint8Array);
salt = salt ? salt : argon2Salt();
const encoded = unicodeEncode(data);
if (encoded.length === 0) {
if (throw_on_empty) {
throw new Error("Cannot encrypt empty data");
} else {
console.warn("Warning: Encrypting empty data, decryption will fail");
}
}
const hash = await argon2Hash(password, salt, 32, 3, 65536, true);
if (!(hash instanceof Uint8Array)) throw new Error("Argon2 hashing failed: " + hash);
let ciphertext, tag;
if (window?.crypto?.subtle) {
const key = await window.crypto.subtle.importKey(
"raw", hash, "AES-GCM", false, ["encrypt"]
);
const result = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv, tagLength: 128 }, key, encoded
);
const resultArray = new Uint8Array(result);
ciphertext = resultArray.slice(0, resultArray.length - 16);
tag = resultArray.slice(resultArray.length - 16);
} else if (window?.AESPonyfill) {
const cipher = new window.AESPonyfill(hash, { mode: "GCM", keyLen: 256 });
const result = await cipher.encrypt(encoded, { iv });
ciphertext = result.ciphertext;
tag = result.tag;
cipher.free();
} else {
throw new Error("No Crypto API with AES256-GCM found");
}
const combined = concatWithLength(salt, iv, tag, new Uint8Array(ciphertext));
return ubase64Encode(combined);
}
export async function decrypt(combinedBase64, password) {
if (window.speedups_loaded instanceof Promise) {
await window.speedups_loaded;
}
const combined = ubase64Decode(combinedBase64);
let [salt, iv, tag, ciphertext] = splitWithLength(combined);
const hash = await argon2Hash(password, salt, 32, 3, 65536, true);
if (!(hash instanceof Uint8Array)) throw new Error("Argon2 hashing failed: " + hash);
let encoded;
if (window?.crypto?.subtle) {
const key = await crypto.subtle.importKey(
"raw", hash, "AES-GCM", false, ["decrypt"]
);
const ciphertextWithTag = new Uint8Array(ciphertext.length + tag.length);
ciphertextWithTag.set(ciphertext);
ciphertextWithTag.set(tag, ciphertext.length);
encoded = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv, tagLength: 16 * 8 }, key, ciphertextWithTag
);
} else if (window?.AESPonyfill) {
const cipher = new window.AESPonyfill(hash, { mode: "GCM", keyLen: 256 });
encoded = await cipher.decrypt(ciphertext, iv, tag);
cipher.free();
} else {
throw new Error("No Crypto API with AES256-GCM found");
}
const result = unicodeDecode(encoded);
return result;
}