mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 15:53:27 +00:00
Use 2 iterations for local password hashing (#404)
* Use 2 iterations for local password hashing * fix typo
This commit is contained in:
@@ -4,6 +4,7 @@ import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||
|
||||
import { ProfileOrganizationResponse } from '../models/response/profileOrganizationResponse';
|
||||
|
||||
import { HashPurpose } from '../enums/hashPurpose';
|
||||
import { KdfType } from '../enums/kdfType';
|
||||
import { KeySuffixOptions } from './storage.service';
|
||||
|
||||
@@ -40,7 +41,7 @@ export abstract class CryptoService {
|
||||
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>;
|
||||
makePinKey: (pin: string, salt: string, kdf: KdfType, kdfIterations: number) => Promise<SymmetricCryptoKey>;
|
||||
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
|
||||
hashPassword: (password: string, key: SymmetricCryptoKey) => Promise<string>;
|
||||
hashPassword: (password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose) => Promise<string>;
|
||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
|
||||
remakeEncKey: (key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
|
||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>;
|
||||
|
||||
4
common/src/enums/hashPurpose.ts
Normal file
4
common/src/enums/hashPurpose.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum HashPurpose {
|
||||
ServerAuthorization = 1,
|
||||
LocalAuthorization = 2,
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { HashPurpose } from '../enums/hashPurpose';
|
||||
import { KdfType } from '../enums/kdfType';
|
||||
import { TwoFactorProviderType } from '../enums/twoFactorProviderType';
|
||||
|
||||
@@ -78,6 +79,7 @@ export const TwoFactorProviders = {
|
||||
export class AuthService implements AuthServiceAbstraction {
|
||||
email: string;
|
||||
masterPasswordHash: string;
|
||||
localMasterPasswordHash: string;
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
ssoRedirectUrl: string;
|
||||
@@ -122,26 +124,28 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
const key = await this.makePreloginKey(masterPassword, email);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
||||
return await this.logInHelper(email, hashedPassword, null, null, null, null, null,
|
||||
const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key,
|
||||
HashPurpose.LocalAuthorization);
|
||||
return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null,
|
||||
key, null, null, null);
|
||||
}
|
||||
|
||||
async logInSso(code: string, codeVerifier: string, redirectUrl: string): Promise<AuthResult> {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
return await this.logInHelper(null, null, code, codeVerifier, redirectUrl, null, null,
|
||||
return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null, null,
|
||||
null, null, null, null);
|
||||
}
|
||||
|
||||
async logInApiKey(clientId: string, clientSecret: string): Promise<AuthResult> {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
return await this.logInHelper(null, null, null, null, null, clientId, clientSecret,
|
||||
return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret,
|
||||
null, null, null, null);
|
||||
}
|
||||
|
||||
async logInTwoFactor(twoFactorProvider: TwoFactorProviderType, twoFactorToken: string,
|
||||
remember?: boolean): Promise<AuthResult> {
|
||||
return await this.logInHelper(this.email, this.masterPasswordHash, this.code, this.codeVerifier,
|
||||
this.ssoRedirectUrl, this.clientId, this.clientSecret, this.key, twoFactorProvider,
|
||||
return await this.logInHelper(this.email, this.masterPasswordHash, this.localMasterPasswordHash, this.code,
|
||||
this.codeVerifier, this.ssoRedirectUrl, this.clientId, this.clientSecret, this.key, twoFactorProvider,
|
||||
twoFactorToken, remember);
|
||||
}
|
||||
|
||||
@@ -150,21 +154,23 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
const key = await this.makePreloginKey(masterPassword, email);
|
||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
||||
return await this.logInHelper(email, hashedPassword, null, null, null, null, null, key,
|
||||
const localHashedPassword = await this.cryptoService.hashPassword(masterPassword, key,
|
||||
HashPurpose.LocalAuthorization);
|
||||
return await this.logInHelper(email, hashedPassword, localHashedPassword, null, null, null, null, null, key,
|
||||
twoFactorProvider, twoFactorToken, remember);
|
||||
}
|
||||
|
||||
async logInSsoComplete(code: string, codeVerifier: string, redirectUrl: string,
|
||||
twoFactorProvider: TwoFactorProviderType, twoFactorToken: string, remember?: boolean): Promise<AuthResult> {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
return await this.logInHelper(null, null, code, codeVerifier, redirectUrl, null,
|
||||
return await this.logInHelper(null, null, null, code, codeVerifier, redirectUrl, null,
|
||||
null, null, twoFactorProvider, twoFactorToken, remember);
|
||||
}
|
||||
|
||||
async logInApiKeyComplete(clientId: string, clientSecret: string, twoFactorProvider: TwoFactorProviderType,
|
||||
twoFactorToken: string, remember?: boolean): Promise<AuthResult> {
|
||||
this.selectedTwoFactorProviderType = null;
|
||||
return await this.logInHelper(null, null, null, null, null, clientId, clientSecret, null,
|
||||
return await this.logInHelper(null, null, null, null, null, null, clientId, clientSecret, null,
|
||||
twoFactorProvider, twoFactorToken, remember);
|
||||
}
|
||||
|
||||
@@ -264,8 +270,8 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
return this.email != null && this.masterPasswordHash != null;
|
||||
}
|
||||
|
||||
private async logInHelper(email: string, hashedPassword: string, code: string, codeVerifier: string,
|
||||
redirectUrl: string, clientId: string, clientSecret: string, key: SymmetricCryptoKey,
|
||||
private async logInHelper(email: string, hashedPassword: string, localHashedPassword: string, code: string,
|
||||
codeVerifier: string, redirectUrl: string, clientId: string, clientSecret: string, key: SymmetricCryptoKey,
|
||||
twoFactorProvider?: TwoFactorProviderType, twoFactorToken?: string, remember?: boolean): Promise<AuthResult> {
|
||||
const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email);
|
||||
const appId = await this.appIdService.getAppId();
|
||||
@@ -314,6 +320,7 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
const twoFactorResponse = response as IdentityTwoFactorResponse;
|
||||
this.email = email;
|
||||
this.masterPasswordHash = hashedPassword;
|
||||
this.localMasterPasswordHash = localHashedPassword;
|
||||
this.code = code;
|
||||
this.codeVerifier = codeVerifier;
|
||||
this.ssoRedirectUrl = redirectUrl;
|
||||
@@ -338,8 +345,8 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
if (key != null) {
|
||||
await this.cryptoService.setKey(key);
|
||||
}
|
||||
if (hashedPassword != null) {
|
||||
await this.cryptoService.setKeyHash(hashedPassword);
|
||||
if (localHashedPassword != null) {
|
||||
await this.cryptoService.setKeyHash(localHashedPassword);
|
||||
}
|
||||
|
||||
// Skip this step during SSO new user flow. No key is returned from server.
|
||||
@@ -373,6 +380,7 @@ export class AuthService implements AuthServiceAbstraction {
|
||||
this.key = null;
|
||||
this.email = null;
|
||||
this.masterPasswordHash = null;
|
||||
this.localMasterPasswordHash = null;
|
||||
this.code = null;
|
||||
this.codeVerifier = null;
|
||||
this.ssoRedirectUrl = null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as bigInt from 'big-integer';
|
||||
|
||||
import { EncryptionType } from '../enums/encryptionType';
|
||||
import { HashPurpose } from '../enums/hashPurpose';
|
||||
import { KdfType } from '../enums/kdfType';
|
||||
|
||||
import { EncArrayBuffer } from '../models/domain/encArrayBuffer';
|
||||
@@ -384,7 +385,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return new SymmetricCryptoKey(sendKey);
|
||||
}
|
||||
|
||||
async hashPassword(password: string, key: SymmetricCryptoKey): Promise<string> {
|
||||
async hashPassword(password: string, key: SymmetricCryptoKey, hashPurpose?: HashPurpose): Promise<string> {
|
||||
if (key == null) {
|
||||
key = await this.getKey();
|
||||
}
|
||||
@@ -392,7 +393,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
throw new Error('Invalid parameters.');
|
||||
}
|
||||
|
||||
const hash = await this.cryptoFunctionService.pbkdf2(key.key, password, 'sha256', 1);
|
||||
const iterations = hashPurpose === HashPurpose.LocalAuthorization ? 2 : 1;
|
||||
const hash = await this.cryptoFunctionService.pbkdf2(key.key, password, 'sha256', iterations);
|
||||
return Utils.fromBufferToB64(hash);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { CryptoService } from '../abstractions/crypto.service';
|
||||
import { I18nService } from '../abstractions/i18n.service';
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from '../abstractions/passwordReprompt.service';
|
||||
|
||||
import { HashPurpose } from '../enums/hashPurpose';
|
||||
|
||||
export class PasswordRepromptService implements PasswordRepromptServiceAbstraction {
|
||||
constructor(private i18nService: I18nService, private cryptoService: CryptoService,
|
||||
private platformUtilService: PlatformUtilsService) { }
|
||||
@@ -14,7 +16,7 @@ export class PasswordRepromptService implements PasswordRepromptServiceAbstracti
|
||||
|
||||
async showPasswordPrompt() {
|
||||
const passwordValidator = async (value: string) => {
|
||||
const keyHash = await this.cryptoService.hashPassword(value, null);
|
||||
const keyHash = await this.cryptoService.hashPassword(value, null, HashPurpose.LocalAuthorization);
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
|
||||
if (storedKeyHash == null || keyHash == null || storedKeyHash !== keyHash) {
|
||||
|
||||
Reference in New Issue
Block a user