mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-24 16:49:26 +01:00
fast decrypt
This commit is contained in:
parent
c6c5dd6d46
commit
de4494e1b3
@ -1,6 +1,7 @@
|
||||
import { NodeCryptoFunctionService } from '../../../src/services/nodeCryptoFunction.service';
|
||||
|
||||
import { Utils } from '../../../src/misc/utils';
|
||||
import { SymmetricCryptoKey } from '../../../src/models/domain/symmetricCryptoKey';
|
||||
|
||||
const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' +
|
||||
'4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' +
|
||||
@ -92,19 +93,20 @@ describe('NodeCrypto Function Service', () => {
|
||||
const value = 'EncryptMe!';
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await nodeCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||
const decValue = await nodeCryptoFunctionService.aesDecryptSmall(encValue, iv.buffer, key.buffer);
|
||||
const decValue = await nodeCryptoFunctionService.aesDecryptLarge(encValue, iv.buffer, key.buffer);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aesDecryptSmall', () => {
|
||||
describe('aesDecryptFast', () => {
|
||||
it('should successfully decrypt data', async () => {
|
||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA==');
|
||||
const decValue = await nodeCryptoFunctionService.aesDecryptSmall(data.buffer, iv.buffer, key.buffer);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!');
|
||||
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
|
||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
|
||||
const data = 'ByUF8vhyX4ddU9gcooznwA==';
|
||||
const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||
const decValue = await nodeCryptoFunctionService.aesDecryptFast(params);
|
||||
expect(decValue).toBe('EncryptMe!');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { PlatformUtilsService } from '../../../src/abstractions/platformUtils.se
|
||||
import { WebCryptoFunctionService } from '../../../src/services/webCryptoFunction.service';
|
||||
|
||||
import { Utils } from '../../../src/misc/utils';
|
||||
import { SymmetricCryptoKey } from '../../../src/models/domain';
|
||||
|
||||
const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' +
|
||||
'4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' +
|
||||
@ -109,8 +110,12 @@ describe('WebCrypto Function Service', () => {
|
||||
const value = 'EncryptMe!';
|
||||
const data = Utils.fromUtf8ToArray(value);
|
||||
const encValue = await webCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||
const decValue = await webCryptoFunctionService.aesDecryptSmall(encValue, iv.buffer, key.buffer);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||
const encData = Utils.fromBufferToB64(encValue);
|
||||
const b64Iv = Utils.fromBufferToB64(iv.buffer);
|
||||
const symKey = new SymmetricCryptoKey(key.buffer);
|
||||
const params = webCryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
|
||||
const decValue = await webCryptoFunctionService.aesDecryptFast(params);
|
||||
expect(decValue).toBe(value);
|
||||
});
|
||||
|
||||
it('should successfully encrypt and then decrypt large data', async () => {
|
||||
@ -125,14 +130,15 @@ describe('WebCrypto Function Service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('aesDecryptSmall', () => {
|
||||
describe('aesDecryptFast', () => {
|
||||
it('should successfully decrypt data', async () => {
|
||||
const webCryptoFunctionService = getWebCryptoFunctionService();
|
||||
const iv = makeStaticByteArray(16);
|
||||
const key = makeStaticByteArray(32);
|
||||
const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA==');
|
||||
const decValue = await webCryptoFunctionService.aesDecryptSmall(data.buffer, iv.buffer, key.buffer);
|
||||
expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!');
|
||||
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
|
||||
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
|
||||
const data = 'ByUF8vhyX4ddU9gcooznwA==';
|
||||
const params = webCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||
const decValue = await webCryptoFunctionService.aesDecryptFast(params);
|
||||
expect(decValue).toBe('EncryptMe!');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -27,7 +27,6 @@ export abstract class CryptoService {
|
||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<CipherString>;
|
||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<CipherString>;
|
||||
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
decrypt: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
decryptToUtf8: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<string>;
|
||||
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||
randomNumber: (min: number, max: number) => Promise<number>;
|
||||
|
@ -1,10 +1,19 @@
|
||||
import { DecryptParameters } from '../models/domain/decryptParameters';
|
||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||
|
||||
export abstract class CryptoFunctionService {
|
||||
pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
||||
iterations: number) => Promise<ArrayBuffer>;
|
||||
hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
||||
hmac: (value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
||||
timeSafeEqual: (a: ArrayBuffer, b: ArrayBuffer) => Promise<boolean>;
|
||||
hmacFast: (value: ArrayBuffer | string, key: ArrayBuffer | string, algorithm: 'sha1' | 'sha256' | 'sha512') =>
|
||||
Promise<ArrayBuffer | string>;
|
||||
timeSafeEqualFast: (a: ArrayBuffer | string, b: ArrayBuffer | string) => Promise<boolean>;
|
||||
aesEncrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
aesDecryptSmall: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
aesDecryptFastParameters: (data: string, iv: string, mac: string, key: SymmetricCryptoKey) =>
|
||||
DecryptParameters<ArrayBuffer | string>;
|
||||
aesDecryptFast: (parameters: DecryptParameters<ArrayBuffer | string>) => Promise<string>;
|
||||
aesDecryptLarge: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||
rsaEncrypt: (data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
||||
rsaDecrypt: (data: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
||||
|
8
src/models/domain/decryptParameters.ts
Normal file
8
src/models/domain/decryptParameters.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export class DecryptParameters<T> {
|
||||
encKey: T;
|
||||
data: T;
|
||||
iv: T;
|
||||
macKey: T;
|
||||
mac: T;
|
||||
macData: T;
|
||||
}
|
@ -9,6 +9,8 @@ export class SymmetricCryptoKey {
|
||||
encType: EncryptionType;
|
||||
|
||||
keyB64: string;
|
||||
encKeyB64: string;
|
||||
macKeyB64: string;
|
||||
|
||||
constructor(key: ArrayBuffer, encType?: EncryptionType) {
|
||||
if (key == null) {
|
||||
@ -26,7 +28,6 @@ export class SymmetricCryptoKey {
|
||||
}
|
||||
|
||||
this.key = key;
|
||||
this.keyB64 = Utils.fromBufferToB64(key);
|
||||
this.encType = encType;
|
||||
|
||||
if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) {
|
||||
@ -41,5 +42,15 @@ export class SymmetricCryptoKey {
|
||||
} else {
|
||||
throw new Error('Unsupported encType/key length.');
|
||||
}
|
||||
|
||||
if (this.key != null) {
|
||||
this.keyB64 = Utils.fromBufferToB64(this.key);
|
||||
}
|
||||
if (this.encKey != null) {
|
||||
this.encKeyB64 = Utils.fromBufferToB64(this.encKey);
|
||||
}
|
||||
if (this.macKey != null) {
|
||||
this.macKeyB64 = Utils.fromBufferToB64(this.macKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -304,7 +304,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
const iv = Utils.fromB64ToArray(cipherString.initializationVector).buffer;
|
||||
const ct = Utils.fromB64ToArray(cipherString.cipherText).buffer;
|
||||
const mac = cipherString.mac ? Utils.fromB64ToArray(cipherString.mac).buffer : null;
|
||||
const decipher = await this.aesDecrypt(cipherString.encryptionType, ct, iv, mac, key);
|
||||
const decipher = await this.aesDecryptToBytes(cipherString.encryptionType, ct, iv, mac, key);
|
||||
if (decipher == null) {
|
||||
return null;
|
||||
}
|
||||
@ -313,8 +313,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
}
|
||||
|
||||
async decryptToUtf8(cipherString: CipherString, key?: SymmetricCryptoKey): Promise<string> {
|
||||
const decipher = await this.decrypt(cipherString, key);
|
||||
return Utils.fromBufferToUtf8(decipher);
|
||||
return await this.aesDecryptToUtf8(cipherString.encryptionType, cipherString.cipherText,
|
||||
cipherString.initializationVector, cipherString.mac, key);
|
||||
}
|
||||
|
||||
async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
@ -351,7 +351,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.aesDecryptLarge(encType, ctBytes.buffer, ivBytes.buffer,
|
||||
return await this.aesDecryptToBytes(encType, ctBytes.buffer, ivBytes.buffer,
|
||||
macBytes != null ? macBytes.buffer : null, key);
|
||||
}
|
||||
|
||||
@ -409,8 +409,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private async aesDecrypt(encType: EncryptionType, ct: ArrayBuffer, iv: ArrayBuffer, mac: ArrayBuffer,
|
||||
key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
private async aesDecryptToUtf8(encType: EncryptionType, ct: string, iv: string, mac: string,
|
||||
key: SymmetricCryptoKey): Promise<string> {
|
||||
const keyForEnc = await this.getKeyForEncryption(key);
|
||||
const theKey = this.resolveLegacyKey(encType, keyForEnc);
|
||||
|
||||
@ -420,51 +420,57 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (encType !== theKey.encType) {
|
||||
if (theKey.encType !== encType) {
|
||||
// tslint:disable-next-line
|
||||
console.error('encType unavailable.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const fastParams = this.cryptoFunctionService.aesDecryptFastParameters(ct, iv, mac, theKey);
|
||||
if (fastParams.macKey != null && fastParams.mac != null) {
|
||||
const computedMac = await this.cryptoFunctionService.hmacFast(fastParams.macData,
|
||||
fastParams.macKey, 'sha256');
|
||||
const macsEqual = await this.cryptoFunctionService.timeSafeEqualFast(fastParams.mac, computedMac);
|
||||
if (!macsEqual) {
|
||||
// tslint:disable-next-line
|
||||
console.error('mac failed.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return this.cryptoFunctionService.aesDecryptFast(fastParams);
|
||||
}
|
||||
|
||||
private async aesDecryptToBytes(encType: EncryptionType, ct: ArrayBuffer, iv: ArrayBuffer,
|
||||
mac: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
const keyForEnc = await this.getKeyForEncryption(key);
|
||||
const theKey = this.resolveLegacyKey(encType, keyForEnc);
|
||||
|
||||
if (theKey.macKey != null && mac == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (theKey.encType !== encType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (theKey.macKey != null && mac != null) {
|
||||
const macData = new Uint8Array(iv.byteLength + ct.byteLength);
|
||||
macData.set(new Uint8Array(iv), 0);
|
||||
macData.set(new Uint8Array(ct), iv.byteLength);
|
||||
const computedMac = await this.cryptoFunctionService.hmac(new Uint8Array(iv).buffer,
|
||||
theKey.macKey, 'sha256');
|
||||
if (!this.macsEqual(computedMac, mac)) {
|
||||
const computedMac = await this.cryptoFunctionService.hmac(macData.buffer, theKey.macKey, 'sha256');
|
||||
if (computedMac === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const macsMatch = await this.cryptoFunctionService.timeSafeEqual(mac, computedMac);
|
||||
if (!macsMatch) {
|
||||
// tslint:disable-next-line
|
||||
console.error('mac failed.');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return this.cryptoFunctionService.aesDecryptSmall(ct, iv, theKey.encKey);
|
||||
}
|
||||
|
||||
private async aesDecryptLarge(encType: EncryptionType, ct: ArrayBuffer, iv: ArrayBuffer,
|
||||
mac: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||
const theKey = await this.getKeyForEncryption(key);
|
||||
if (theKey.macKey == null || mac == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const macData = new Uint8Array(iv.byteLength + ct.byteLength);
|
||||
macData.set(new Uint8Array(iv), 0);
|
||||
macData.set(new Uint8Array(ct), iv.byteLength);
|
||||
const computedMac = await this.cryptoFunctionService.hmac(new Uint8Array(iv).buffer,
|
||||
theKey.macKey, 'sha256');
|
||||
if (computedMac === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const macsMatch = await this.macsEqual(mac, computedMac);
|
||||
if (macsMatch === false) {
|
||||
// tslint:disable-next-line
|
||||
console.error('mac failed.');
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.cryptoFunctionService.aesDecryptLarge(ct, iv, theKey.encKey);
|
||||
}
|
||||
|
||||
@ -509,7 +515,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
if (key != null && key.macKey != null && encPieces.length > 1) {
|
||||
const mac = Utils.fromB64ToArray(encPieces[1]).buffer;
|
||||
const computedMac = await this.cryptoFunctionService.hmac(ct, key.macKey, 'sha256');
|
||||
const macsEqual = await this.macsEqual(mac, computedMac);
|
||||
const macsEqual = await this.cryptoFunctionService.timeSafeEqual(mac, computedMac);
|
||||
if (!macsEqual) {
|
||||
throw new Error('MAC failed.');
|
||||
}
|
||||
@ -536,28 +542,6 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||
return this.cryptoFunctionService.rsaDecrypt(ct, privateKey, alg);
|
||||
}
|
||||
|
||||
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
private async macsEqual(mac1: ArrayBuffer, mac2: ArrayBuffer): Promise<boolean> {
|
||||
const key = await this.cryptoFunctionService.randomBytes(32);
|
||||
const newMac1 = await this.cryptoFunctionService.hmac(mac1, key, 'sha256');
|
||||
const newMac2 = await this.cryptoFunctionService.hmac(mac2, key, 'sha256');
|
||||
if (newMac1.byteLength !== newMac2.byteLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const arr1 = new Uint8Array(newMac1);
|
||||
const arr2 = new Uint8Array(newMac2);
|
||||
for (let i = 0; i < arr2.length; i++) {
|
||||
if (arr1[i] !== arr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async getKeyForEncryption(key?: SymmetricCryptoKey): Promise<SymmetricCryptoKey> {
|
||||
if (key != null) {
|
||||
return key;
|
||||
|
@ -6,6 +6,9 @@ import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
|
||||
|
||||
import { Utils } from '../misc/utils';
|
||||
|
||||
import { DecryptParameters } from '../models/domain/decryptParameters';
|
||||
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||
|
||||
export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
||||
iterations: number): Promise<ArrayBuffer> {
|
||||
@ -38,6 +41,33 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
return Promise.resolve(this.toArrayBuffer(hmac.digest()));
|
||||
}
|
||||
|
||||
async timeSafeEqual(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
||||
const key = await this.randomBytes(32);
|
||||
const mac1 = await this.hmac(a, key, 'sha256');
|
||||
const mac2 = await this.hmac(b, key, 'sha256');
|
||||
if (mac1.byteLength !== mac2.byteLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const arr1 = new Uint8Array(mac1);
|
||||
const arr2 = new Uint8Array(mac2);
|
||||
for (let i = 0; i < arr2.length; i++) {
|
||||
if (arr1[i] !== arr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hmacFast(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> {
|
||||
return this.hmac(value, key, algorithm);
|
||||
}
|
||||
|
||||
timeSafeEqualFast(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
||||
return this.timeSafeEqual(a, b);
|
||||
}
|
||||
|
||||
aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const nodeData = this.toNodeBuffer(data);
|
||||
const nodeIv = this.toNodeBuffer(iv);
|
||||
@ -47,8 +77,31 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
return Promise.resolve(this.toArrayBuffer(encBuf));
|
||||
}
|
||||
|
||||
aesDecryptSmall(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
return this.aesDecryptLarge(data, iv, key);
|
||||
aesDecryptFastParameters(data: string, iv: string, mac: string, key: SymmetricCryptoKey):
|
||||
DecryptParameters<ArrayBuffer> {
|
||||
const p = new DecryptParameters<ArrayBuffer>();
|
||||
p.encKey = key.encKey;
|
||||
p.data = Utils.fromB64ToArray(data).buffer;
|
||||
p.iv = Utils.fromB64ToArray(iv).buffer;
|
||||
|
||||
const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength);
|
||||
macData.set(new Uint8Array(p.iv), 0);
|
||||
macData.set(new Uint8Array(p.data), p.iv.byteLength);
|
||||
p.macData = macData.buffer;
|
||||
|
||||
if (key.macKey != null) {
|
||||
p.macKey = key.macKey;
|
||||
}
|
||||
if (mac != null) {
|
||||
p.mac = Utils.fromB64ToArray(mac).buffer;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
async aesDecryptFast(parameters: DecryptParameters<ArrayBuffer>): Promise<string> {
|
||||
const decBuf = await this.aesDecryptLarge(parameters.data, parameters.iv, parameters.encKey);
|
||||
return Utils.fromBufferToUtf8(decBuf);
|
||||
}
|
||||
|
||||
aesDecryptLarge(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
|
@ -5,6 +5,9 @@ import { PlatformUtilsService } from '../abstractions/platformUtils.service';
|
||||
|
||||
import { Utils } from '../misc/utils';
|
||||
|
||||
import { SymmetricCryptoKey } from '../models/domain';
|
||||
import { DecryptParameters } from '../models/domain/decryptParameters';
|
||||
|
||||
export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
private crypto: Crypto;
|
||||
private subtle: SubtleCrypto;
|
||||
@ -80,21 +83,93 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return await this.subtle.sign(signingAlgorithm, impKey, value);
|
||||
}
|
||||
|
||||
// Safely compare two values in a way that protects against timing attacks (Double HMAC Verification).
|
||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
|
||||
async timeSafeEqual(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
||||
const macKey = await this.randomBytes(32);
|
||||
const signingAlgorithm = {
|
||||
name: 'HMAC',
|
||||
hash: { name: 'SHA-256' },
|
||||
};
|
||||
const impKey = await this.subtle.importKey('raw', macKey, signingAlgorithm, false, ['sign']);
|
||||
const mac1 = await this.subtle.sign(signingAlgorithm, impKey, a);
|
||||
const mac2 = await this.subtle.sign(signingAlgorithm, impKey, b);
|
||||
|
||||
if (mac1.byteLength !== mac2.byteLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const arr1 = new Uint8Array(mac1);
|
||||
const arr2 = new Uint8Array(mac2);
|
||||
for (let i = 0; i < arr2.length; i++) {
|
||||
if (arr1[i] !== arr2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
hmacFast(value: string, key: string, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<string> {
|
||||
const hmac = (forge as any).hmac.create();
|
||||
hmac.start(algorithm, key);
|
||||
hmac.update(value);
|
||||
const bytes = hmac.digest().getBytes();
|
||||
return Promise.resolve(bytes);
|
||||
}
|
||||
|
||||
async timeSafeEqualFast(a: string, b: string): Promise<boolean> {
|
||||
const rand = await this.randomBytes(32);
|
||||
const bytes = new Uint32Array(rand);
|
||||
const buffer = forge.util.createBuffer();
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
buffer.putInt32(bytes[i]);
|
||||
}
|
||||
const macKey = buffer.getBytes();
|
||||
|
||||
const hmac = (forge as any).hmac.create();
|
||||
hmac.start('sha256', macKey);
|
||||
hmac.update(a);
|
||||
const mac1 = hmac.digest().getBytes();
|
||||
|
||||
hmac.start(null, null);
|
||||
hmac.update(b);
|
||||
const mac2 = hmac.digest().getBytes();
|
||||
|
||||
const equals = mac1 === mac2;
|
||||
return equals;
|
||||
}
|
||||
|
||||
async aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const impKey = await this.subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['encrypt']);
|
||||
return await this.subtle.encrypt({ name: 'AES-CBC', iv: iv }, impKey, data);
|
||||
}
|
||||
|
||||
async aesDecryptSmall(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
const dataBytes = this.toByteString(data);
|
||||
const ivBytes = this.toByteString(iv);
|
||||
const keyBytes = this.toByteString(key);
|
||||
const dataBuffer = (forge as any).util.createBuffer(dataBytes);
|
||||
const decipher = (forge as any).cipher.createDecipher('AES-CBC', keyBytes);
|
||||
decipher.start({ iv: ivBytes });
|
||||
aesDecryptFastParameters(data: string, iv: string, mac: string, key: SymmetricCryptoKey):
|
||||
DecryptParameters<string> {
|
||||
const p = new DecryptParameters<string>();
|
||||
p.encKey = forge.util.decode64(key.encKeyB64);
|
||||
p.data = forge.util.decode64(data);
|
||||
p.iv = forge.util.decode64(iv);
|
||||
p.macData = p.iv + p.data;
|
||||
if (key.macKeyB64 != null) {
|
||||
p.macKey = forge.util.decode64(key.macKeyB64);
|
||||
}
|
||||
if (mac != null) {
|
||||
p.mac = forge.util.decode64(mac);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
aesDecryptFast(parameters: DecryptParameters<string>): Promise<string> {
|
||||
const dataBuffer = (forge as any).util.createBuffer(parameters.data);
|
||||
const decipher = (forge as any).cipher.createDecipher('AES-CBC', parameters.encKey);
|
||||
decipher.start({ iv: parameters.iv });
|
||||
decipher.update(dataBuffer);
|
||||
decipher.finish();
|
||||
return Utils.fromByteStringToArray(decipher.output.getBytes()).buffer;
|
||||
const val = decipher.output.toString('utf8');
|
||||
return Promise.resolve(val);
|
||||
}
|
||||
|
||||
async aesDecryptLarge(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
|
Loading…
Reference in New Issue
Block a user