pubnub
Version:
Publish & Subscribe Real-time Messaging with PubNub
121 lines (87 loc) • 3.98 kB
JavaScript
/** @flow */
import crypto, { type $CryptoKey } from 'isomorphic-webcrypto';
import type { ICryptography } from '../';
import type { IFile, FileClass } from '../../file';
function concatArrayBuffer(ab1: ArrayBuffer, ab2: ArrayBuffer): ArrayBuffer {
const tmp = new Uint8Array(ab1.byteLength + ab2.byteLength);
tmp.set(new Uint8Array(ab1), 0);
tmp.set(new Uint8Array(ab2), ab1.byteLength);
return tmp.buffer;
}
export default class WebCryptography implements ICryptography<ArrayBuffer | string> {
static IV_LENGTH = 16;
get algo() {
return 'aes-256-cbc';
}
async encrypt(key: string, input: ArrayBuffer | string): Promise<ArrayBuffer | string> {
const cKey = await this.getKey(key);
if (input instanceof ArrayBuffer) {
return this.encryptArrayBuffer(cKey, input);
} else if (typeof input === 'string') {
return this.encryptString(cKey, input);
} else {
throw new Error('Cannot encrypt this file. In browsers file encryption supports only string or ArrayBuffer');
}
}
async decrypt(key: string, input: ArrayBuffer | string): Promise<ArrayBuffer | string> {
const cKey = await this.getKey(key);
if (input instanceof ArrayBuffer) {
return this.decryptArrayBuffer(cKey, input);
} else if (typeof input === 'string') {
return this.decryptString(cKey, input);
} else {
throw new Error('Cannot decrypt this file. In browsers file decryption supports only string or ArrayBuffer');
}
}
async encryptFile(key: string, file: IFile, File: FileClass): Promise<IFile> {
const bKey = await this.getKey(key);
const abPlaindata = await file.toArrayBuffer();
const abCipherdata = await this.encryptArrayBuffer(bKey, abPlaindata);
return File.create({
name: file.name,
mimeType: 'application/octet-stream',
data: abCipherdata,
});
}
async decryptFile(key: string, file: IFile, File: FileClass): Promise<IFile> {
const bKey = await this.getKey(key);
const abCipherdata = await file.toArrayBuffer();
const abPlaindata = await this.decryptArrayBuffer(bKey, abCipherdata);
if (file.data instanceof ArrayBuffer) {
return File.create({
name: file.name,
data: abPlaindata,
});
} else {
throw new Error('Cannot decrypt this file. In browser environment file decryption supports only ArrayBuffer.');
}
}
async getKey(key: string): Promise<$CryptoKey> {
const bKey = Buffer.from(key);
const abHash = await crypto.subtle.digest('SHA-256', bKey.buffer);
const abKey = Buffer.from(Buffer.from(abHash).toString('hex').slice(0, 32), 'utf8').buffer;
return crypto.subtle.importKey('raw', abKey, 'AES-CBC', true, ['encrypt', 'decrypt']);
}
async encryptArrayBuffer(key: $CryptoKey, plaintext: ArrayBuffer) {
const abIv = crypto.getRandomValues(new Uint8Array(16));
return concatArrayBuffer(abIv.buffer, await crypto.subtle.encrypt({ name: 'AES-CBC', iv: abIv }, key, plaintext));
}
async decryptArrayBuffer(key: $CryptoKey, ciphertext: ArrayBuffer) {
const abIv = ciphertext.slice(0, 16);
return crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, ciphertext.slice(16));
}
async encryptString(key: $CryptoKey, plaintext: string) {
const abIv = crypto.getRandomValues(new Uint8Array(16));
const abPlaintext = Buffer.from(plaintext).buffer;
const abPayload = await crypto.subtle.encrypt({ name: 'AES-CBC', iv: abIv }, key, abPlaintext);
const ciphertext = concatArrayBuffer(abIv.buffer, abPayload);
return Buffer.from(ciphertext).toString('utf8');
}
async decryptString(key: $CryptoKey, ciphertext: string) {
const abCiphertext = Buffer.from(ciphertext);
const abIv = abCiphertext.slice(0, 16);
const abPayload = abCiphertext.slice(16);
const abPlaintext = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: abIv }, key, abPayload);
return Buffer.from(abPlaintext).toString('utf8');
}
}