1
0
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:
Bernd Schoolmann
2023-01-26 15:20:12 +01:00
committed by GitHub
parent d02af23b30
commit e055e68991
18 changed files with 343 additions and 107 deletions

View File

@@ -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,

View File

@@ -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;

View File

@@ -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.");
}

View File

@@ -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,

View File

@@ -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;
}
}