fast decrypt
This commit is contained in:
parent
c6c5dd6d46
commit
de4494e1b3
|
@ -1,6 +1,7 @@
|
||||||
import { NodeCryptoFunctionService } from '../../../src/services/nodeCryptoFunction.service';
|
import { NodeCryptoFunctionService } from '../../../src/services/nodeCryptoFunction.service';
|
||||||
|
|
||||||
import { Utils } from '../../../src/misc/utils';
|
import { Utils } from '../../../src/misc/utils';
|
||||||
|
import { SymmetricCryptoKey } from '../../../src/models/domain/symmetricCryptoKey';
|
||||||
|
|
||||||
const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' +
|
const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' +
|
||||||
'4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' +
|
'4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' +
|
||||||
|
@ -92,19 +93,20 @@ describe('NodeCrypto Function Service', () => {
|
||||||
const value = 'EncryptMe!';
|
const value = 'EncryptMe!';
|
||||||
const data = Utils.fromUtf8ToArray(value);
|
const data = Utils.fromUtf8ToArray(value);
|
||||||
const encValue = await nodeCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
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);
|
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('aesDecryptSmall', () => {
|
describe('aesDecryptFast', () => {
|
||||||
it('should successfully decrypt data', async () => {
|
it('should successfully decrypt data', async () => {
|
||||||
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
const iv = makeStaticByteArray(16);
|
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
|
||||||
const key = makeStaticByteArray(32);
|
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
|
||||||
const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA==');
|
const data = 'ByUF8vhyX4ddU9gcooznwA==';
|
||||||
const decValue = await nodeCryptoFunctionService.aesDecryptSmall(data.buffer, iv.buffer, key.buffer);
|
const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||||
expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!');
|
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 { WebCryptoFunctionService } from '../../../src/services/webCryptoFunction.service';
|
||||||
|
|
||||||
import { Utils } from '../../../src/misc/utils';
|
import { Utils } from '../../../src/misc/utils';
|
||||||
|
import { SymmetricCryptoKey } from '../../../src/models/domain';
|
||||||
|
|
||||||
const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' +
|
const RsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP' +
|
||||||
'4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' +
|
'4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP' +
|
||||||
|
@ -109,8 +110,12 @@ describe('WebCrypto Function Service', () => {
|
||||||
const value = 'EncryptMe!';
|
const value = 'EncryptMe!';
|
||||||
const data = Utils.fromUtf8ToArray(value);
|
const data = Utils.fromUtf8ToArray(value);
|
||||||
const encValue = await webCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
const encValue = await webCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
|
||||||
const decValue = await webCryptoFunctionService.aesDecryptSmall(encValue, iv.buffer, key.buffer);
|
const encData = Utils.fromBufferToB64(encValue);
|
||||||
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
|
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 () => {
|
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 () => {
|
it('should successfully decrypt data', async () => {
|
||||||
const webCryptoFunctionService = getWebCryptoFunctionService();
|
const webCryptoFunctionService = getWebCryptoFunctionService();
|
||||||
const iv = makeStaticByteArray(16);
|
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
|
||||||
const key = makeStaticByteArray(32);
|
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
|
||||||
const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA==');
|
const data = 'ByUF8vhyX4ddU9gcooznwA==';
|
||||||
const decValue = await webCryptoFunctionService.aesDecryptSmall(data.buffer, iv.buffer, key.buffer);
|
const params = webCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
|
||||||
expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!');
|
const decValue = await webCryptoFunctionService.aesDecryptFast(params);
|
||||||
|
expect(decValue).toBe('EncryptMe!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ export abstract class CryptoService {
|
||||||
makeEncKey: (key: SymmetricCryptoKey) => Promise<CipherString>;
|
makeEncKey: (key: SymmetricCryptoKey) => Promise<CipherString>;
|
||||||
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<CipherString>;
|
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<CipherString>;
|
||||||
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||||
decrypt: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
|
||||||
decryptToUtf8: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<string>;
|
decryptToUtf8: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<string>;
|
||||||
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
|
||||||
randomNumber: (min: number, max: number) => Promise<number>;
|
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 {
|
export abstract class CryptoFunctionService {
|
||||||
pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
||||||
iterations: number) => Promise<ArrayBuffer>;
|
iterations: number) => Promise<ArrayBuffer>;
|
||||||
hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise<ArrayBuffer>;
|
||||||
hmac: (value: ArrayBuffer, key: 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>;
|
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>;
|
aesDecryptLarge: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
|
||||||
rsaEncrypt: (data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
rsaEncrypt: (data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
||||||
rsaDecrypt: (data: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
rsaDecrypt: (data: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256') => Promise<ArrayBuffer>;
|
||||||
|
|
|
@ -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;
|
encType: EncryptionType;
|
||||||
|
|
||||||
keyB64: string;
|
keyB64: string;
|
||||||
|
encKeyB64: string;
|
||||||
|
macKeyB64: string;
|
||||||
|
|
||||||
constructor(key: ArrayBuffer, encType?: EncryptionType) {
|
constructor(key: ArrayBuffer, encType?: EncryptionType) {
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
|
@ -26,7 +28,6 @@ export class SymmetricCryptoKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.keyB64 = Utils.fromBufferToB64(key);
|
|
||||||
this.encType = encType;
|
this.encType = encType;
|
||||||
|
|
||||||
if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) {
|
if (encType === EncryptionType.AesCbc256_B64 && key.byteLength === 32) {
|
||||||
|
@ -41,5 +42,15 @@ export class SymmetricCryptoKey {
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unsupported encType/key length.');
|
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 iv = Utils.fromB64ToArray(cipherString.initializationVector).buffer;
|
||||||
const ct = Utils.fromB64ToArray(cipherString.cipherText).buffer;
|
const ct = Utils.fromB64ToArray(cipherString.cipherText).buffer;
|
||||||
const mac = cipherString.mac ? Utils.fromB64ToArray(cipherString.mac).buffer : null;
|
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) {
|
if (decipher == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -313,8 +313,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptToUtf8(cipherString: CipherString, key?: SymmetricCryptoKey): Promise<string> {
|
async decryptToUtf8(cipherString: CipherString, key?: SymmetricCryptoKey): Promise<string> {
|
||||||
const decipher = await this.decrypt(cipherString, key);
|
return await this.aesDecryptToUtf8(cipherString.encryptionType, cipherString.cipherText,
|
||||||
return Utils.fromBufferToUtf8(decipher);
|
cipherString.initializationVector, cipherString.mac, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
||||||
|
@ -351,7 +351,7 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
return null;
|
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);
|
macBytes != null ? macBytes.buffer : null, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,8 +409,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async aesDecrypt(encType: EncryptionType, ct: ArrayBuffer, iv: ArrayBuffer, mac: ArrayBuffer,
|
private async aesDecryptToUtf8(encType: EncryptionType, ct: string, iv: string, mac: string,
|
||||||
key: SymmetricCryptoKey): Promise<ArrayBuffer> {
|
key: SymmetricCryptoKey): Promise<string> {
|
||||||
const keyForEnc = await this.getKeyForEncryption(key);
|
const keyForEnc = await this.getKeyForEncryption(key);
|
||||||
const theKey = this.resolveLegacyKey(encType, keyForEnc);
|
const theKey = this.resolveLegacyKey(encType, keyForEnc);
|
||||||
|
|
||||||
|
@ -420,51 +420,57 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encType !== theKey.encType) {
|
if (theKey.encType !== encType) {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
console.error('encType unavailable.');
|
console.error('encType unavailable.');
|
||||||
return null;
|
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) {
|
if (theKey.macKey != null && mac != null) {
|
||||||
const macData = new Uint8Array(iv.byteLength + ct.byteLength);
|
const macData = new Uint8Array(iv.byteLength + ct.byteLength);
|
||||||
macData.set(new Uint8Array(iv), 0);
|
macData.set(new Uint8Array(iv), 0);
|
||||||
macData.set(new Uint8Array(ct), iv.byteLength);
|
macData.set(new Uint8Array(ct), iv.byteLength);
|
||||||
const computedMac = await this.cryptoFunctionService.hmac(new Uint8Array(iv).buffer,
|
const computedMac = await this.cryptoFunctionService.hmac(macData.buffer, theKey.macKey, 'sha256');
|
||||||
theKey.macKey, 'sha256');
|
if (computedMac === null) {
|
||||||
if (!this.macsEqual(computedMac, mac)) {
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const macsMatch = await this.cryptoFunctionService.timeSafeEqual(mac, computedMac);
|
||||||
|
if (!macsMatch) {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
console.error('mac failed.');
|
console.error('mac failed.');
|
||||||
return null;
|
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);
|
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) {
|
if (key != null && key.macKey != null && encPieces.length > 1) {
|
||||||
const mac = Utils.fromB64ToArray(encPieces[1]).buffer;
|
const mac = Utils.fromB64ToArray(encPieces[1]).buffer;
|
||||||
const computedMac = await this.cryptoFunctionService.hmac(ct, key.macKey, 'sha256');
|
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) {
|
if (!macsEqual) {
|
||||||
throw new Error('MAC failed.');
|
throw new Error('MAC failed.');
|
||||||
}
|
}
|
||||||
|
@ -536,28 +542,6 @@ export class CryptoService implements CryptoServiceAbstraction {
|
||||||
return this.cryptoFunctionService.rsaDecrypt(ct, privateKey, alg);
|
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> {
|
private async getKeyForEncryption(key?: SymmetricCryptoKey): Promise<SymmetricCryptoKey> {
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
return key;
|
return key;
|
||||||
|
|
|
@ -6,6 +6,9 @@ import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
|
||||||
|
|
||||||
import { Utils } from '../misc/utils';
|
import { Utils } from '../misc/utils';
|
||||||
|
|
||||||
|
import { DecryptParameters } from '../models/domain/decryptParameters';
|
||||||
|
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
||||||
|
|
||||||
export class NodeCryptoFunctionService implements CryptoFunctionService {
|
export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||||
pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
||||||
iterations: number): Promise<ArrayBuffer> {
|
iterations: number): Promise<ArrayBuffer> {
|
||||||
|
@ -38,6 +41,33 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||||
return Promise.resolve(this.toArrayBuffer(hmac.digest()));
|
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> {
|
aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||||
const nodeData = this.toNodeBuffer(data);
|
const nodeData = this.toNodeBuffer(data);
|
||||||
const nodeIv = this.toNodeBuffer(iv);
|
const nodeIv = this.toNodeBuffer(iv);
|
||||||
|
@ -47,8 +77,31 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||||
return Promise.resolve(this.toArrayBuffer(encBuf));
|
return Promise.resolve(this.toArrayBuffer(encBuf));
|
||||||
}
|
}
|
||||||
|
|
||||||
aesDecryptSmall(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
aesDecryptFastParameters(data: string, iv: string, mac: string, key: SymmetricCryptoKey):
|
||||||
return this.aesDecryptLarge(data, iv, key);
|
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> {
|
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 { Utils } from '../misc/utils';
|
||||||
|
|
||||||
|
import { SymmetricCryptoKey } from '../models/domain';
|
||||||
|
import { DecryptParameters } from '../models/domain/decryptParameters';
|
||||||
|
|
||||||
export class WebCryptoFunctionService implements CryptoFunctionService {
|
export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||||
private crypto: Crypto;
|
private crypto: Crypto;
|
||||||
private subtle: SubtleCrypto;
|
private subtle: SubtleCrypto;
|
||||||
|
@ -80,21 +83,93 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||||
return await this.subtle.sign(signingAlgorithm, impKey, value);
|
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> {
|
async aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||||
const impKey = await this.subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['encrypt']);
|
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);
|
return await this.subtle.encrypt({ name: 'AES-CBC', iv: iv }, impKey, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async aesDecryptSmall(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
aesDecryptFastParameters(data: string, iv: string, mac: string, key: SymmetricCryptoKey):
|
||||||
const dataBytes = this.toByteString(data);
|
DecryptParameters<string> {
|
||||||
const ivBytes = this.toByteString(iv);
|
const p = new DecryptParameters<string>();
|
||||||
const keyBytes = this.toByteString(key);
|
p.encKey = forge.util.decode64(key.encKeyB64);
|
||||||
const dataBuffer = (forge as any).util.createBuffer(dataBytes);
|
p.data = forge.util.decode64(data);
|
||||||
const decipher = (forge as any).cipher.createDecipher('AES-CBC', keyBytes);
|
p.iv = forge.util.decode64(iv);
|
||||||
decipher.start({ iv: ivBytes });
|
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.update(dataBuffer);
|
||||||
decipher.finish();
|
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> {
|
async aesDecryptLarge(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
||||||
|
|
Loading…
Reference in New Issue