crypto-service
Version:
crypto-service
172 lines (156 loc) • 5.7 kB
text/typescript
import atob from 'atob';
import btoa from 'btoa';
import * as forge from 'node-forge';
export interface KeypairResult {
rsaEncrypted: string;
data: {
shareKey: string;
salt: string;
};
}
export interface CryptoOption {
isEncoded64?: boolean;
convertKey2Bytes?: boolean;
}
export interface CryptoPort {
exchangeKeypair: (pk: string) => Promise<KeypairResult>;
encryptAESMessage: (aesKey: string, salt: string, dataJSON: string, options?: CryptoOption) => Promise<string>;
decryptAESMessage: <T>(messageEncrypted: string, clientShKey: string, clientSalt: string, options?: CryptoOption) => Promise<T | string>;
encrypt: (pk: any, message: string) => string;
decrypt: (sk: any, data: string) => string;
encryptAES: (key: string, message: string) => string;
decryptAES: (key: string, ciphertext: string, iv: string) => string;
hashHMAC: (key: string, data: string) => string;
hashData: (text: string) => string;
}
export class CryptoService {
constructor(private encryption: boolean, private hashKey: string) {
this.exchangeKeypair = this.exchangeKeypair.bind(this);
this.encryptAESMessage = this.encryptAESMessage.bind(this);
this.decryptAESMessage = this.decryptAESMessage.bind(this);
this.encrypt = this.encrypt.bind(this);
this.decrypt = this.decrypt.bind(this);
this.encryptAES = this.encryptAES.bind(this);
this.decryptAES = this.decryptAES.bind(this);
this.hashHMAC = this.hashHMAC.bind(this);
this.hashData = this.hashData.bind(this);
}
async exchangeKeypair(pk: string): Promise<KeypairResult> {
const shareKeyServ = forge.random.getBytesSync(32);
const saltServ = forge.random.getBytesSync(32);
const data = {
shareKey: shareKeyServ,
salt: saltServ,
};
const rsaEncrypted = this.encrypt(pk, JSON.stringify(data));
return {
rsaEncrypted,
data,
};
}
async encryptAESMessage(aesKey: string, salt: string, dataJSON: string, options?: CryptoOption): Promise<string> {
if (this.encryption) {
const convertKey2Bytes = options && options.convertKey2Bytes;
const aesEncrypted = this.encryptAES(
convertKey2Bytes ? atob(aesKey) : aesKey,
dataJSON,
);
const hash = this.hashHMAC(
convertKey2Bytes ? atob(salt) : salt,
aesEncrypted,
);
const reqData = `${hash}${aesEncrypted}`;
const base64 = btoa(reqData);
return base64;
} else {
return dataJSON;
}
}
async decryptAESMessage<T>(messageEncrypted: string, clientShKey: string, clientSalt: string, options?: CryptoOption): Promise<T | string> {
if (this.encryption) {
if (options && options.isEncoded64) {
messageEncrypted = atob(messageEncrypted);
}
const convertKey2Bytes = options && options.convertKey2Bytes;
const hashData = messageEncrypted.substring(0, 64);
const output = messageEncrypted.substring(64, messageEncrypted.length);
const hmacResult = this.hashHMAC(
convertKey2Bytes ? atob(clientSalt) : clientSalt,
output,
);
if (hashData !== hmacResult) {
return '';
}
const ivRes = output.substring(0, 16);
const msg = output.substring(16, output.length);
const decrypted = await this.decryptAES(
convertKey2Bytes ? atob(clientShKey) : clientShKey,
msg,
ivRes,
);
return decrypted;
} else {
return messageEncrypted;
}
}
encrypt(pk: any, message: string): string {
const publicKey = forge.pki.publicKeyFromPem(pk);
const encrypted = publicKey.encrypt(message, 'RSA-OAEP', {
md: forge.md.sha256.create(),
});
const res = btoa(encrypted);
return res;
}
decrypt(sk: any, data: string): string {
const privateKey = forge.pki.privateKeyFromPem(sk);
const msg = forge.util.decode64(data);
const decrypted = privateKey.decrypt(msg, 'RSA-OAEP', {
md: forge.md.sha256.create(),
});
return decrypted;
}
encryptAES(key: string, message: string): string {
const iv = forge.random.getBytesSync(16);
const cipher = forge.cipher.createCipher('AES-CBC', key);
cipher.start({ iv });
cipher.update(forge.util.createBuffer(message, 'utf8'));
cipher.finish();
const encrypted = cipher.output;
const strEncrypted = Buffer.from(encrypted.getBytes()).toString();
const result = iv + strEncrypted;
return result;
}
decryptAES(key: string, ciphertext: string, iv: string): string {
const decipher = forge.cipher.createDecipher('AES-CBC', key);
decipher.start({ iv });
decipher.update(forge.util.createBuffer(ciphertext));
decipher.finish();
return decipher.output.toString();
}
hashHMAC(key: string, data: string): string {
const hmac1 = forge.hmac.create();
hmac1.start('sha256', key);
hmac1.update(data);
return hmac1.digest().toHex();
}
hashData(text: string): string {
const crypto = require('crypto');
const shasum = crypto.createHash('sha256');
const msg = text + this.hashKey;
shasum.update(msg);
const hash = shasum.digest('hex');
return hash;
}
}
export function genUuid(): string {
const crypto = require('crypto');
const id = crypto.randomBytes(16).toString('hex');
return id;
}
export function getTokenExpirationTime(now: Date, tokenAge: number) {
return (now.getTime() + tokenAge) / 1000;
}
export function minuteToMillisec(min: number) {
const msInOneMin = 1000 * 60;
return min * msInOneMin;
}