Skip to content

Commit 82efed9

Browse files
committed
Export encryption details
Details of encryption methods used are now exported.
1 parent e66f3ca commit 82efed9

2 files changed

Lines changed: 107 additions & 41 deletions

File tree

src/index.ts

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
type DetailedEncryptionResult = {
22
vault: string;
33
exportedKeyString: string;
4+
keyDerivationOptions: KeyDerivationOptions;
45
};
56

67
type EncryptionResult = {
@@ -35,7 +36,7 @@ export async function encrypt<R>(
3536
key?: CryptoKey,
3637
salt: string = generateSalt(),
3738
): Promise<string> {
38-
const cryptoKey = key || (await keyFromPassword(password, salt));
39+
const cryptoKey = key || (await keyFromPassword({ password, salt })).key;
3940
const payload = await encryptWithKey(cryptoKey, dataObj);
4041
payload.salt = salt;
4142
return JSON.stringify(payload);
@@ -55,13 +56,17 @@ export async function encryptWithDetail<R>(
5556
dataObj: R,
5657
salt = generateSalt(),
5758
): Promise<DetailedEncryptionResult> {
58-
const key = await keyFromPassword(password, salt);
59+
const { key, keyDerivationOptions } = await keyFromPassword({
60+
password,
61+
salt,
62+
});
5963
const exportedKeyString = await exportKey(key);
6064
const vault = await encrypt(password, dataObj, key, salt);
6165

6266
return {
6367
vault,
6468
exportedKeyString,
69+
keyDerivationOptions,
6570
};
6671
}
6772

@@ -117,7 +122,7 @@ export async function decrypt(
117122
const payload = JSON.parse(text);
118123
const { salt } = payload;
119124

120-
const cryptoKey = key || (await keyFromPassword(password, salt));
125+
const cryptoKey = key || (await keyFromPassword({ password, salt })).key;
121126

122127
const result = await decryptWithKey(cryptoKey, payload);
123128
return result;
@@ -129,15 +134,22 @@ export async function decrypt(
129134
*
130135
* @param password - The password to decrypt with.
131136
* @param text - The encrypted vault to decrypt.
137+
* @param keyDerivationOptions - Key derivation options.
132138
* @returns The decrypted vault along with the salt and exported key.
133139
*/
134140
export async function decryptWithDetail(
135141
password: string,
136142
text: string,
143+
keyDerivationOptions?: KeyDerivationOptions,
137144
): Promise<DetailedDecryptResult> {
138145
const payload = JSON.parse(text);
139146
const { salt } = payload;
140-
const key = await keyFromPassword(password, salt);
147+
148+
const { key } = await keyFromPassword({
149+
password,
150+
salt,
151+
...(keyDerivationOptions || {}),
152+
});
141153
const exportedKeyString = await exportKey(key);
142154
const vault = await decrypt(password, text, key);
143155

@@ -211,42 +223,96 @@ export async function exportKey(key: CryptoKey): Promise<string> {
211223
return JSON.stringify(exportedKey);
212224
}
213225

226+
type AllowedImportAlgorithms = 'PBKDF2';
227+
type AllowedDerivationAlgorithms = {
228+
name: 'PBKDF2';
229+
iterations: 10000;
230+
hash: 'SHA-256';
231+
};
232+
type AllowedDerivedKeyAlgorithms = {
233+
name: 'AES-GCM';
234+
length: 256;
235+
};
236+
237+
type KeyDerivationOptions = {
238+
/**
239+
* The algorithm used to import a key from the password
240+
* (see {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey}).
241+
*/
242+
importAlgorithm?: AllowedImportAlgorithms;
243+
/**
244+
* The algorithm used to derive an encryption/decryption key
245+
* from the imported key (see {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey}).
246+
*/
247+
derivationAlgorithm?: AllowedDerivationAlgorithms;
248+
/**
249+
* The algorithm the derived key will be used for
250+
* (see {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey}).
251+
*/
252+
derivedKeyAlgorithm?: AllowedDerivedKeyAlgorithms;
253+
};
254+
214255
/**
215256
* Generate a CryptoKey from a password and random salt.
216257
*
217-
* @param password - The password to use to generate key.
218-
* @param salt - The salt string to use in key derivation.
219-
* @returns A CryptoKey for encryption and decryption.
258+
* @param options - Key derivation options.
259+
* @param options.keyDerivationOptions - Key derivation options.
260+
* @param options.password - The password to use to generate key.
261+
* @param options.salt - The salt string to use in key derivation.
262+
* @returns The derived key, along with all encryption options used.
220263
*/
221-
export async function keyFromPassword(
222-
password: string,
223-
salt: string,
224-
): Promise<CryptoKey> {
264+
export async function keyFromPassword({
265+
keyDerivationOptions = {},
266+
password,
267+
salt,
268+
}: {
269+
keyDerivationOptions?: KeyDerivationOptions;
270+
password: string;
271+
salt: string;
272+
}): Promise<{
273+
keyDerivationOptions: KeyDerivationOptions;
274+
key: CryptoKey;
275+
}> {
225276
const passBuffer = Buffer.from(password, STRING_ENCODING);
226277
const saltBuffer = Buffer.from(salt, 'base64');
278+
const importAlgorithm = keyDerivationOptions.importAlgorithm || 'PBKDF2';
279+
const derivationAlgorithm = keyDerivationOptions.derivationAlgorithm || {
280+
name: 'PBKDF2',
281+
iterations: 10000,
282+
hash: 'SHA-256',
283+
};
284+
const derivedKeyAlgorithm = keyDerivationOptions.derivedKeyAlgorithm || {
285+
name: 'AES-GCM',
286+
length: 256,
287+
};
227288

228289
const key = await global.crypto.subtle.importKey(
229290
'raw',
230291
passBuffer,
231-
{ name: 'PBKDF2' },
292+
importAlgorithm,
232293
false,
233294
['deriveBits', 'deriveKey'],
234295
);
235296

236297
const derivedKey = await global.crypto.subtle.deriveKey(
237298
{
238-
name: 'PBKDF2',
299+
...derivationAlgorithm,
239300
salt: saltBuffer,
240-
iterations: 10000,
241-
hash: 'SHA-256',
242301
},
243302
key,
244-
{ name: DERIVED_KEY_FORMAT, length: 256 },
303+
derivedKeyAlgorithm,
245304
true,
246305
['encrypt', 'decrypt'],
247306
);
248307

249-
return derivedKey;
308+
return {
309+
key: derivedKey,
310+
keyDerivationOptions: {
311+
importAlgorithm,
312+
derivationAlgorithm,
313+
derivedKeyAlgorithm,
314+
},
315+
};
250316
}
251317

252318
/**

test/index.spec.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,10 @@ test('encryptor:encrypt using key then decrypt', async ({ page }) => {
216216

217217
const encryptedData = await page.evaluate(
218218
async (args) => {
219-
const key = await window.encryptor.keyFromPassword(
220-
args.password,
221-
args.salt,
222-
);
219+
const { key } = await window.encryptor.keyFromPassword({
220+
password: args.password,
221+
salt: args.salt,
222+
});
223223
return await window.encryptor.encryptWithKey(key, args.data);
224224
},
225225
{ data, password, salt },
@@ -248,10 +248,10 @@ test('encryptor:encrypt using key then decrypt using wrong password', async ({
248248

249249
const encryptedData = await page.evaluate(
250250
async (args) => {
251-
const key = await window.encryptor.keyFromPassword(
252-
args.password,
253-
args.salt,
254-
);
251+
const { key } = await window.encryptor.keyFromPassword({
252+
password: args.password,
253+
salt: args.salt,
254+
});
255255
return await window.encryptor.encryptWithKey(key, args.data);
256256
},
257257
{ data, password, salt },
@@ -288,10 +288,10 @@ test('encryptor:encrypt then decrypt using key', async ({ page }) => {
288288

289289
const decryptedData = await page.evaluate(
290290
async (args) => {
291-
const key = await window.encryptor.keyFromPassword(
292-
args.password,
293-
args.salt,
294-
);
291+
const { key } = await window.encryptor.keyFromPassword({
292+
password: args.password,
293+
salt: args.salt,
294+
});
295295
return await window.encryptor.decryptWithKey(key, args.encryptedPayload);
296296
},
297297
{ encryptedPayload, password, salt },
@@ -319,10 +319,10 @@ test('encryptor:encrypt then decrypt using key derived from wrong password', asy
319319
await expect(
320320
page.evaluate(
321321
async (args) => {
322-
const key = await window.encryptor.keyFromPassword(
323-
args.wrongPassword,
324-
args.salt,
325-
);
322+
const { key } = await window.encryptor.keyFromPassword({
323+
password: args.wrongPassword,
324+
salt: args.salt,
325+
});
326326
return await window.encryptor.decryptWithKey(
327327
key,
328328
args.encryptedPayload,
@@ -344,10 +344,10 @@ test('encryptor:decrypt encrypted data using key', async ({ page }) => {
344344

345345
const decryptedData = await page.evaluate(
346346
async (args) => {
347-
const key = await window.encryptor.keyFromPassword(
348-
args.password,
349-
args.salt,
350-
);
347+
const { key } = await window.encryptor.keyFromPassword({
348+
password: args.password,
349+
salt: args.salt,
350+
});
351351
return await window.encryptor.decryptWithKey(key, args.encryptedPayload);
352352
},
353353
{ encryptedPayload, password, salt },
@@ -369,10 +369,10 @@ test('encryptor:decrypt encrypted data using key derived from wrong password', a
369369
await expect(
370370
page.evaluate(
371371
async (args) => {
372-
const key = await window.encryptor.keyFromPassword(
373-
args.wrongPassword,
374-
args.salt,
375-
);
372+
const { key } = await window.encryptor.keyFromPassword({
373+
password: args.wrongPassword,
374+
salt: args.salt,
375+
});
376376
return await window.encryptor.decryptWithKey(
377377
key,
378378
args.encryptedPayload,

0 commit comments

Comments
 (0)