From 4f17d72ea731280f348d078c9a9750b5b5c7e41d Mon Sep 17 00:00:00 2001 From: cupcakearmy Date: Mon, 8 Apr 2019 20:06:55 +0200 Subject: [PATCH] rely on native api --- src/AES.ts | 164 ----------------------------------------------- src/RSA.ts | 39 ----------- src/Symmetric.ts | 157 +++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 12 ++-- 4 files changed, 163 insertions(+), 209 deletions(-) delete mode 100644 src/AES.ts delete mode 100644 src/RSA.ts create mode 100644 src/Symmetric.ts diff --git a/src/AES.ts b/src/AES.ts deleted file mode 100644 index be61756..0000000 --- a/src/AES.ts +++ /dev/null @@ -1,164 +0,0 @@ -import forge from 'node-forge' - -export enum Ciphers { - AES_256_GCM, - AES_192_GCM, - AES_128_GCM, - AES_256_CTR, - AES_192_CTR, - AES_128_CTR, - AES_256_CBC, - AES_192_CBC, - AES_128_CBC, -} - -type CipherConfig = { - alg: forge.cipher.Algorithm, - saltSize: number - keySize: number - tagSize: number - it: number -} - -type EncryptedItem = { - alg: forge.cipher.Algorithm, - data: string, - keySize: number, - iv: string, - it: number - salt: string, - tag: string, - tagSize: number, -} - -export default class AES { - - static Ciphers = Ciphers - - static encrypt(data: string, key: string, type: Ciphers = Ciphers.AES_256_GCM): string { - const { alg, it, keySize, saltSize, tagSize } = AES.getCipherConfig(type) - const salt = forge.random.getBytesSync(saltSize) - const iv = forge.random.getBytesSync(keySize) - - const cipher = forge.cipher.createCipher( - alg, - forge.pkcs5.pbkdf2(key, salt, it, keySize), - ) - - cipher.start({ - iv, - tagLength: tagSize, - }) - cipher.update(forge.util.createBuffer(data)) - cipher.finish() - - const encrypted: EncryptedItem = { - alg, - data: forge.util.hexToBytes(cipher.output.toHex()), - keySize, - iv, - it, - salt, - tag: forge.util.hexToBytes(cipher.mode.tag.toHex()), - tagSize, - } - - return Buffer.from(JSON.stringify(encrypted)).toString('base64') - } - - static decrypt(e: string, key: string): string { - const { alg, data, keySize, iv, it, salt, tag, tagSize }: EncryptedItem = JSON.parse(Buffer.from(e, 'base64').toString()) - - const cipher = forge.cipher.createCipher( - alg, - forge.pkcs5.pbkdf2(key, salt, it, keySize), - ) - - cipher.start({ - iv, - tag: new forge.util.ByteStringBuffer(tag), - tagLength: tagSize, - }) - cipher.update(new forge.util.ByteStringBuffer(data)) - cipher.finish() - - return cipher.output.toString() - } - - private static getCipherConfig = (type: Ciphers): CipherConfig => { - switch (type) { - case Ciphers.AES_128_GCM: - return { - alg: 'AES-GCM', - saltSize: 128, - keySize: 16, - tagSize: 128, - it: 2 ** 12, - } - case Ciphers.AES_192_GCM: - return { - alg: 'AES-GCM', - saltSize: 128, - keySize: 24, - tagSize: 128, - it: 2 ** 12, - } - case Ciphers.AES_256_GCM: - return { - alg: 'AES-GCM', - saltSize: 128, - keySize: 32, - tagSize: 128, - it: 2 ** 12, - } - case Ciphers.AES_128_CBC: - return { - alg: 'AES-CBC', - saltSize: 128, - keySize: 16, - tagSize: 128, - it: 2 ** 12, - } - case Ciphers.AES_192_CBC: - return { - alg: 'AES-CBC', - saltSize: 128, - keySize: 24, - tagSize: 128, - it: 2 ** 12, - } - case Ciphers.AES_256_CBC: - return { - alg: 'AES-CBC', - saltSize: 128, - keySize: 32, - tagSize: 128, - it: 2 ** 12, - } - case Ciphers.AES_128_CTR: - return { - alg: 'AES-CTR', - saltSize: 128, - keySize: 16, - tagSize: 128, - it: 2 ** 12, - } - case Ciphers.AES_192_CTR: - return { - alg: 'AES-CTR', - saltSize: 128, - keySize: 24, - tagSize: 128, - it: 2 ** 12, - } - case Ciphers.AES_256_CTR: - return { - alg: 'AES-CTR', - saltSize: 128, - keySize: 32, - tagSize: 128, - it: 2 ** 12, - } - } - } -} \ No newline at end of file diff --git a/src/RSA.ts b/src/RSA.ts deleted file mode 100644 index 38bb8a9..0000000 --- a/src/RSA.ts +++ /dev/null @@ -1,39 +0,0 @@ -import forge from 'node-forge' -import { Base64 } from './Util' - -type PrivateKey = string -type PublicKey = string - -export type KeyPair = { - pub: PublicKey - prv: PrivateKey -} - - -export default class RSA { - - static gen = (size: number = 2 ** 12): Promise => new Promise((resolve, reject) => { - forge.pki.rsa.generateKeyPair({ bits: size }, function (err, keypair) { - if (err) reject() - else resolve({ - pub: forge.pki.publicKeyToPem(keypair.publicKey), - prv: forge.pki.privateKeyToPem(keypair.privateKey), - }) - }) - }) - - static encrypt(data: string, key: PublicKey): string { - const obj = forge.pki.publicKeyFromPem(key) - if (obj instanceof ArrayBuffer) throw new Error() - - return Base64.encode(obj.encrypt(data)) - } - - static decrypt(data: string, key: PrivateKey): string { - const obj = forge.pki.privateKeyFromPem(key) - if (obj instanceof ArrayBuffer) throw new Error() - - return obj.decrypt(Base64.decode(data)) - } - -} \ No newline at end of file diff --git a/src/Symmetric.ts b/src/Symmetric.ts new file mode 100644 index 0000000..3cdbb92 --- /dev/null +++ b/src/Symmetric.ts @@ -0,0 +1,157 @@ +import { + Cipher, + CipherCCM, + CipherGCM, + createCipheriv, + createDecipheriv, + Decipher, + DecipherCCM, + DecipherGCM, + randomBytes, + scryptSync, +} from 'crypto' +import { TransformOptions } from 'stream' + +import { Base64 } from './Util' + +export enum Ciphers { + ChaCha20, + ChaCha20_Poly1305, + AES_256_GCM, + AES_192_GCM, + AES_128_GCM, + AES_256_CTR, + AES_192_CTR, + AES_128_CTR, +} + +type CipherConfig = { + alg: string, + keySize: number, + ivSize: number + mac?: number +} + +type EncryptedItem = { + alg: string, + data: string, + iv: string, + salt: string + keySize: number, + tag?: string, + tagSize?: number +} + +export default class Symmetric { + + static Ciphers = Ciphers + static Encoding: BufferEncoding = 'base64' + + static encrypt(data: string, pass: string, type: Ciphers = Ciphers.AES_256_CTR): string { + const { alg, ivSize, mac, keySize } = Symmetric.getCipherConfig(type) + + const iv = randomBytes(ivSize) + const salt = randomBytes(keySize) + const key = scryptSync(pass, salt, keySize) + + // @ts-ignore + const options: TransformOptions = mac ? { authTagLength: mac } : undefined + const cipher: CipherGCM | CipherCCM | Cipher = createCipheriv(alg, key, iv, options) + let content: Buffer = Buffer.concat([ + cipher.update(data), + cipher.final(), + ]) + + let tag: string | undefined = undefined + // @ts-ignore + if (mac) tag = cipher.getAuthTag().toString(Symmetric.Encoding) + + const encrypted: EncryptedItem = { + alg, + data: content.toString(Symmetric.Encoding), + tag, + iv: iv.toString(Symmetric.Encoding), + salt: salt.toString(Symmetric.Encoding), + keySize, + tagSize: mac, + } + + return Base64.encode(JSON.stringify(encrypted)) + } + + static decrypt(e: string, pass: string): string { + const { alg, data, iv, tag, salt, keySize, tagSize }: EncryptedItem = JSON.parse(Base64.decode(e)) + const key = scryptSync(pass, Buffer.from(salt, Symmetric.Encoding), keySize) + + // @ts-ignore + const options: TransformOptions = tag ? { authTagLength: tagSize } : undefined + const decipher: DecipherGCM | DecipherCCM | Decipher = createDecipheriv(alg, key, Buffer.from(iv, Symmetric.Encoding), options) + + // @ts-ignore + if (tag) decipher.setAuthTag(Buffer.from(tag, Symmetric.Encoding)) + + const decrypted: Buffer = Buffer.concat([ + decipher.update(Buffer.from(data, Symmetric.Encoding)), + decipher.final(), + ]) + + return decrypted.toString() + } + + private static getCipherConfig = (type: Ciphers): CipherConfig => { + switch (type) { + case Ciphers.AES_128_GCM: + return { + alg: 'aes-128-gcm', + ivSize: 16, + keySize: 16, + mac: 16, + } + case Ciphers.AES_192_GCM: + return { + alg: 'aes-192-gcm', + ivSize: 16, + keySize: 24, + mac: 16, + } + case Ciphers.AES_256_GCM: + return { + alg: 'aes-256-gcm', + ivSize: 16, + keySize: 32, + mac: 16, + } + case Ciphers.AES_128_CTR: + return { + alg: 'aes-128-ctr', + ivSize: 16, + keySize: 16, + } + case Ciphers.AES_192_CTR: + return { + alg: 'aes-192-ctr', + ivSize: 16, + keySize: 24, + } + case Ciphers.AES_256_CTR: + return { + alg: 'aes-256-ctr', + ivSize: 16, + keySize: 32, + } + case Ciphers.ChaCha20: + return { + alg: 'chacha20', + ivSize: 16, + keySize: 32, + } + case Ciphers.ChaCha20_Poly1305: + return { + alg: 'chacha20-poly1305', + ivSize: 16, + keySize: 32, + mac: 8, + } + } + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 3b3fc35..19155c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,12 @@ -import AES from './AES' import RSA from './RSA' +import Symmetric from './Symmetric' -export default { +const exp = { RSA, - AES, + Symmetric, } -export { - AES, - RSA, +module.exports = { + ...exp, + default: exp, } \ No newline at end of file