mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 23:33:31 +00:00
[PS-2251] Implement argon2 kdf (#4468)
* Implement argon2 * Remove argon2 webassembly warning * Replace magic numbers by enum * move packages * cleanup call to argon2 * update call to node argon2 * don't need wasm-eval * revert config changes * Update libs/common/src/enums/kdfType.ts Co-authored-by: Martin Weinelt <mweinelt@users.noreply.github.com> * Update kdfType.ts * apply DEFAULT_PBKDF2_ITERATIONS * checkIfWasmSupported Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com> Co-authored-by: Martin Weinelt <mweinelt@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,13 @@ export abstract class CryptoFunctionService {
|
||||
algorithm: "sha256" | "sha512",
|
||||
iterations: number
|
||||
) => Promise<ArrayBuffer>;
|
||||
argon2: (
|
||||
password: string | ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
iterations: number,
|
||||
memory: number,
|
||||
parallelism: number
|
||||
) => Promise<ArrayBuffer>;
|
||||
hkdf: (
|
||||
ikm: ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
export enum KdfType {
|
||||
PBKDF2_SHA256 = 0,
|
||||
Argon2id = 1,
|
||||
}
|
||||
|
||||
export const DEFAULT_ARGON2_MEMORY = 19;
|
||||
export const DEFAULT_ARGON2_PARALLELISM = 1;
|
||||
export const DEFAULT_ARGON2_ITERATIONS = 2;
|
||||
|
||||
export const DEFAULT_KDF_TYPE = KdfType.PBKDF2_SHA256;
|
||||
export const DEFAULT_KDF_ITERATIONS = 600000;
|
||||
export const DEFAULT_PBKDF2_ITERATIONS = 600000;
|
||||
export const SEND_KDF_ITERATIONS = 100000;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { PlatformUtilsService } from "../abstractions/platformUtils.service";
|
||||
import { StateService } from "../abstractions/state.service";
|
||||
import { EncryptionType } from "../enums/encryptionType";
|
||||
import { HashPurpose } from "../enums/hashPurpose";
|
||||
import { KdfType } from "../enums/kdfType";
|
||||
import { DEFAULT_ARGON2_ITERATIONS, KdfType } from "../enums/kdfType";
|
||||
import { KeySuffixOptions } from "../enums/keySuffixOptions";
|
||||
import { sequentialize } from "../misc/sequentialize";
|
||||
import { Utils } from "../misc/utils";
|
||||
@@ -418,6 +418,19 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
throw new Error("PBKDF2 iteration minimum is 5000.");
|
||||
}
|
||||
key = await this.cryptoFunctionService.pbkdf2(password, salt, "sha256", kdfIterations);
|
||||
} else if (kdf == KdfType.Argon2id) {
|
||||
if (kdfIterations == null) {
|
||||
kdfIterations = DEFAULT_ARGON2_ITERATIONS;
|
||||
} else if (kdfIterations < 2) {
|
||||
throw new Error("Argon2 iteration minimum is 2.");
|
||||
}
|
||||
key = await this.cryptoFunctionService.argon2(
|
||||
password,
|
||||
salt,
|
||||
kdfIterations,
|
||||
16 * 1024, // convert to KiB from MiB
|
||||
2
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unknown Kdf.");
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "../abstractions/export.service";
|
||||
import { FolderService } from "../abstractions/folder/folder.service.abstraction";
|
||||
import { CipherType } from "../enums/cipherType";
|
||||
import { DEFAULT_KDF_ITERATIONS, KdfType } from "../enums/kdfType";
|
||||
import { DEFAULT_PBKDF2_ITERATIONS, KdfType } from "../enums/kdfType";
|
||||
import { Utils } from "../misc/utils";
|
||||
import { CipherData } from "../models/data/cipher.data";
|
||||
import { CollectionData } from "../models/data/collection.data";
|
||||
@@ -54,7 +54,7 @@ export class ExportService implements ExportServiceAbstraction {
|
||||
: await this.getExport("json");
|
||||
|
||||
const salt = Utils.fromBufferToB64(await this.cryptoFunctionService.randomBytes(16));
|
||||
const kdfIterations = DEFAULT_KDF_ITERATIONS;
|
||||
const kdfIterations = DEFAULT_PBKDF2_ITERATIONS;
|
||||
const key = await this.cryptoService.makePinKey(
|
||||
password,
|
||||
salt,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as argon2 from "argon2-browser";
|
||||
import * as forge from "node-forge";
|
||||
|
||||
import { CryptoFunctionService } from "../abstractions/cryptoFunction.service";
|
||||
@@ -8,11 +9,13 @@ import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
|
||||
export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
private crypto: Crypto;
|
||||
private subtle: SubtleCrypto;
|
||||
private wasmSupported: boolean;
|
||||
|
||||
constructor(win: Window | typeof global) {
|
||||
this.crypto = typeof win.crypto !== "undefined" ? win.crypto : null;
|
||||
this.subtle =
|
||||
!!this.crypto && typeof win.crypto.subtle !== "undefined" ? win.crypto.subtle : null;
|
||||
this.wasmSupported = this.checkIfWasmSupported();
|
||||
}
|
||||
|
||||
async pbkdf2(
|
||||
@@ -42,6 +45,32 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return await this.subtle.deriveBits(pbkdf2Params, impKey, wcLen);
|
||||
}
|
||||
|
||||
async argon2(
|
||||
password: string | ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
iterations: number,
|
||||
memory: number,
|
||||
parallelism: number
|
||||
): Promise<ArrayBuffer> {
|
||||
if (!this.wasmSupported) {
|
||||
throw "Webassembly support is required for the Argon2 KDF feature.";
|
||||
}
|
||||
|
||||
const passwordArr = new Uint8Array(this.toBuf(password));
|
||||
const saltArr = new Uint8Array(this.toBuf(salt));
|
||||
|
||||
const result = await argon2.hash({
|
||||
pass: passwordArr,
|
||||
salt: saltArr,
|
||||
time: iterations,
|
||||
mem: memory,
|
||||
parallelism: parallelism,
|
||||
hashLen: 32,
|
||||
type: argon2.ArgonType.Argon2id,
|
||||
});
|
||||
return result.hash;
|
||||
}
|
||||
|
||||
async hkdf(
|
||||
ikm: ArrayBuffer,
|
||||
salt: string | ArrayBuffer,
|
||||
@@ -353,4 +382,21 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
}
|
||||
return algorithm === "sha1" ? "SHA-1" : algorithm === "sha256" ? "SHA-256" : "SHA-512";
|
||||
}
|
||||
|
||||
// ref: https://stackoverflow.com/a/47880734/1090359
|
||||
private checkIfWasmSupported(): boolean {
|
||||
try {
|
||||
if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") {
|
||||
const module = new WebAssembly.Module(
|
||||
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
|
||||
);
|
||||
if (module instanceof WebAssembly.Module) {
|
||||
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user