-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
127 lines (107 loc) · 3.66 KB
/
index.js
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
127
import crypto from "crypto";
import { promisify } from "util";
const CURRENT_VERSION = "1";
const ENCRYPTION_VERSION = {
[CURRENT_VERSION]: {
saltLength: 256,
iterations: 5_000,
keyLength: 32,
digestAlgorithm: "sha256",
ivLength: 16,
cipherAlgorithm: "aes-256-cbc",
decrypt: null,
},
};
const pbkdf2 = promisify(crypto.pbkdf2);
/**
* Encrypt a string with a password.
*
* @param {string} password - plain text password
* @param {string} text - text to encrypt
* @returns {string}
*/
export const encrypt = async (password, text) =>
encryptIterations(password, ENCRYPTION_VERSION[CURRENT_VERSION].iterations, text);
/**
* Encrypt a string with a password and a custom number of KDF iterations.
*
* @param {string} password - plain text password
* @param {number} iterations - iterations used to generate key
* @param {string} text - text to encrypt
* @returns {string}
*/
export const encryptIterations = async (password, iterations, text) => {
if (typeof password !== "string" || password.length == 0)
throw new TypeError(`Invalid password: ${password}`);
if (typeof iterations !== "number" || iterations <= 0 )
throw new TypeError(`Invalid number of iterations: ${iterations}`);
if (typeof text !== "string" || text.length == 0)
throw new TypeError(`Invalid text to encrypt: ${text}`);
let version, salt, key, iv, cipher, cipherText, payload;
version = ENCRYPTION_VERSION[CURRENT_VERSION];
salt = crypto.randomBytes(version.saltLength);
key = await pbkdf2(
password,
salt,
iterations,
version.keyLength,
version.digestAlgorithm
);
iv = crypto.randomBytes(version.ivLength);
cipher = crypto.createCipheriv(version.cipherAlgorithm, key, iv);
cipherText = cipher.update(text);
payload = Buffer.concat([cipherText, cipher.final()]);
return [
CURRENT_VERSION,
`${salt.toString("hex")},${iterations},${iv.toString("hex")}`,
payload.toString("hex")
].join(":");
}
const decrypt1 = async (password, manifest, payload) => {
let version, tokens, salt, iterations, iv, key, decipher, decipherText;
version = ENCRYPTION_VERSION["1"];
tokens = manifest.split(",");
salt = Buffer.from(tokens[0], "hex");
iterations = parseInt(tokens[1], 10);
iv = Buffer.from(tokens[2], "hex");
key = await pbkdf2(
password,
salt,
iterations,
version.keyLength,
version.digestAlgorithm,
);
decipher = crypto.createDecipheriv(version.cipherAlgorithm, key, iv);
decipherText = decipher.update(Buffer.from(payload, "hex"));
return Buffer
.concat([decipherText, decipher.final()])
.toString();
};
ENCRYPTION_VERSION["1"].decrypt = decrypt1;
/**
* Decrypt a string encrypted with encrypt().
*
* @param {string} password - password used to generate encryption key
* @param {string} encrypted- string produced by encrypt()
* @returns {string}
*/
export const decrypt = async (password, encrypted) => {
if (typeof password !== "string" || password.length == 0)
throw new TypeError(`Invalid password: ${password}`);
if (typeof encrypted !== "string" || encrypted.length == 0)
throw new TypeError(`Invalid encrypted text: ${encrypted}`);
let cursor, next, i, fragment, version;
cursor = 0;
fragment = [];
for (i = 0; i < 2; i++) {
next = encrypted.indexOf(":", cursor);
fragment.push(encrypted.slice(cursor, next));
cursor = next + 1;
}
fragment.push(encrypted.slice(cursor));
version = ENCRYPTION_VERSION[fragment[0]];
if (!version) {
throw new Error("String was encrypted with a newer version of @spaceaardvark/encrypt. Upgrade to decrypt this string.");
}
return await version.decrypt(password, fragment[1], fragment[2]);
};