import * as constants from 'constants'; import * as crypto from 'crypto'; import * as forge from 'node-forge'; 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 { const len = algorithm === 'sha256' ? 32 : 64; const nodePassword = this.toNodeValue(password); const nodeSalt = this.toNodeValue(salt); return new Promise((resolve, reject) => { crypto.pbkdf2(nodePassword, nodeSalt, iterations, len, algorithm, (error, key) => { if (error != null) { reject(error); } else { resolve(this.toArrayBuffer(key)); } }); }); } hash(value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { const nodeValue = this.toNodeValue(value); const hash = crypto.createHash(algorithm); hash.update(nodeValue); return Promise.resolve(this.toArrayBuffer(hash.digest())); } hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { const nodeValue = this.toNodeBuffer(value); const nodeKey = this.toNodeBuffer(key); const hmac = crypto.createHmac(algorithm, nodeKey); hmac.update(nodeValue); return Promise.resolve(this.toArrayBuffer(hmac.digest())); } async compare(a: ArrayBuffer, b: ArrayBuffer): Promise { 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 { return this.hmac(value, key, algorithm); } compareFast(a: ArrayBuffer, b: ArrayBuffer): Promise { return this.compare(a, b); } aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { const nodeData = this.toNodeBuffer(data); const nodeIv = this.toNodeBuffer(iv); const nodeKey = this.toNodeBuffer(key); const cipher = crypto.createCipheriv('aes-256-cbc', nodeKey, nodeIv); const encBuf = Buffer.concat([cipher.update(nodeData), cipher.final()]); return Promise.resolve(this.toArrayBuffer(encBuf)); } aesDecryptFastParameters(data: string, iv: string, mac: string, key: SymmetricCryptoKey): DecryptParameters { const p = new DecryptParameters(); 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): Promise { const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey); return Utils.fromBufferToUtf8(decBuf); } aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { const nodeData = this.toNodeBuffer(data); const nodeIv = this.toNodeBuffer(iv); const nodeKey = this.toNodeBuffer(key); const decipher = crypto.createDecipheriv('aes-256-cbc', nodeKey, nodeIv); const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]); return Promise.resolve(this.toArrayBuffer(decBuf)); } rsaEncrypt(data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise { let md: forge.md.MessageDigest; if (algorithm === 'sha256') { md = forge.md.sha256.create(); } else { md = forge.md.sha1.create(); } const dataBytes = Utils.fromBufferToByteString(data); const key = this.toForgePublicKey(publicKey); const decBytes: string = key.encrypt(dataBytes, 'RSA-OAEP', { md: md }); return Promise.resolve(Utils.fromByteStringToArray(decBytes).buffer); } rsaDecrypt(data: ArrayBuffer, privateKey: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise { let md: forge.md.MessageDigest; if (algorithm === 'sha256') { md = forge.md.sha256.create(); } else { md = forge.md.sha1.create(); } const dataBytes = Utils.fromBufferToByteString(data); const key = this.toForgePrivateKey(privateKey); const decBytes: string = key.decrypt(dataBytes, 'RSA-OAEP', { md: md }); return Promise.resolve(Utils.fromByteStringToArray(decBytes).buffer); } randomBytes(length: number): Promise { return new Promise((resolve, reject) => { crypto.randomBytes(length, (error, bytes) => { if (error != null) { reject(error); } else { resolve(this.toArrayBuffer(bytes)); } }); }); } private toNodeValue(value: string | ArrayBuffer): string | Buffer { let nodeValue: string | Buffer; if (typeof (value) === 'string') { nodeValue = value; } else { nodeValue = this.toNodeBuffer(value); } return nodeValue; } private toNodeBuffer(value: ArrayBuffer): Buffer { return Buffer.from(new Uint8Array(value) as any); } private toArrayBuffer(buf: Buffer): ArrayBuffer { return new Uint8Array(buf).buffer; } private toForgePrivateKey(key: ArrayBuffer): any { const byteString = Utils.fromBufferToByteString(key); const asn1 = forge.asn1.fromDer(byteString); return (forge as any).pki.privateKeyFromAsn1(asn1); } private toForgePublicKey(key: ArrayBuffer): any { const byteString = Utils.fromBufferToByteString(key); const asn1 = forge.asn1.fromDer(byteString); return (forge as any).pki.publicKeyFromAsn1(asn1); } }