/* * Copyright (C) Sapphirecode - All Rights Reserved * This file is part of crypto-helper which is released under MIT. * See file 'LICENSE' for full license details. * Created by Timo Hocker , May 2020 */ // @ts-nocheck 'use strict'; const crypto = require ('crypto'); const encoding = require ('@sapphirecode/encoding-helper'); const encryption_mode_cbc_256 = { algorithm: 'aes-256-cbc', nonce_size: 16, key_size: 32, hash: 'sha256', salt_size: 16, iterations: 32767 }; const encryption_mode_cbc_256_quick = { algorithm: 'aes-256-cbc', nonce_size: 16, key_size: 32, hash: 'sha256', salt_size: 16, iterations: 32 }; const encryption_mode_cbc_128 = { algorithm: 'aes-128-cbc', nonce_size: 16, key_size: 16, hash: 'sha256', salt_size: 16, iterations: 40 }; /** * creates a random string * * @param {number} len string length default: 8 * @returns {string} random string */ function random_string (len = 8) { if (len < 1) throw new Error ('invalid length'); return crypto.randomBytes (Math.ceil (len * 3 / 4)) .toString ('base64') .substr (0, len); } /** * creates a random hexadecimal string * * @param {number} len length * @returns {string} hex string */ function random_hex (len = 8) { if (len < 1) throw new Error ('invalid length'); return crypto.randomBytes (Math.ceil (len / 2)) .toString ('hex') .substr (0, len); } /** * creates a 64 character long random hex string * * @returns {string} salt */ function create_salt () { return random_hex (64); } /** * creates a sha512 hash * * @param {string} str string input * @param {string} salt salt * @returns {string} salt */ function hash_sha512 (str, salt) { const md = crypto.createHash ('sha512'); md.update (str); md.update (salt); return md.digest ('hex'); } /** * sign an object * * @param {any} obj object to sign * @param {string} key key to use * @param {string|Object} key_info key identifier * @returns {string} signed object */ function sign_object (obj, key, key_info = null) { const payload = { iat: Date.now (), obj, ...(typeof key_info === 'object' ? key_info : { key_info }) }; const str = encoding.to_b64 (JSON.stringify (payload)); const token = encoding.to_b64 (hash_sha512 (str, key), 'hex'); const res = `${str}.${token}`; return encodeURIComponent (res); } /** * verify a signed object and return its info and contents * * @param {string} str string to verify * @param {string|(Object)=>string} key used key * @param {number} timeout timeout (optional) * @returns {any} returns object if successful, else null */ function verify_signature_get_info (str, key, timeout = 0) { if (typeof str !== 'string') return null; const dec = decodeURIComponent (str) .split ('.'); const json = JSON.parse (encoding.to_utf8 (dec[0], 'base64')); const token = encoding.to_hex (dec[1], 'base64'); const string_key = typeof key === 'string' ? key : key (json); const verify_token = hash_sha512 (dec[0], string_key); if (token !== verify_token) return null; const time = Date.now () - json.iat; if (timeout !== 0 && time > timeout) return null; return json; } /** * verify a signed object and return its contents * * @param {string} str string to verify * @param {string|(Object)=>string} key used key * @param {number} timeout timeout (optional) * @returns {any} returns object if successful, else null */ function verify_signature (str, key, timeout = 0) { const res = verify_signature_get_info (str, key, timeout); if (res === null) return null; return res.obj; } /** * get a signed object info and data * * @param {string} str string to decode * @returns {any} data */ function get_signature_info (str) { if (typeof str !== 'string') return null; const dec = decodeURIComponent (str) .split ('.'); const json = JSON.parse (encoding.to_utf8 (dec[0], 'base64')); return json; } /** * decode a signed object without verifying the signature * * @param {string} str string to decode * @returns {any} object */ function decode_signed (str) { const info = get_signature_info (str); if (info) return info.obj; return null; } /** * creates a sha256 hash * * @param {any} data input * @returns {string} hash */ function checksum (data) { const md = crypto.createHash ('sha256'); md.update (String (data)); return md.digest ('hex'); } /** * encrypt plain text with aes * * @param {string} text plaintext * @param {string} pass password * @param {object} mode encryption mode * @returns {string} encrypted */ function encrypt_aes (text, pass, mode = encryption_mode_cbc_256) { const salt = crypto.randomBytes (mode.salt_size); // eslint-disable-next-line no-sync const key = crypto.pbkdf2Sync ( Buffer.from (pass), salt, mode.iterations, mode.key_size, mode.hash ); const nonce = crypto.randomBytes (mode.nonce_size); const cipher = crypto.createCipheriv (mode.algorithm, key, nonce); return Buffer.concat ([ salt, nonce, cipher.update (Buffer.from (text)), cipher.final () ]) .toString ('base64'); } /** * decrypt an aes string * * @param {string} ciphertext encrypted text * @param {string} pass password * @param {object} mode encryption mode * @param {boolean} rethrow rethrow exceptions instead of returning null * @returns {string} plaintext */ function decrypt_aes ( ciphertext, pass, mode = encryption_mode_cbc_256, rethrow = false ) { try { let buf = Buffer.from (ciphertext, 'base64'); const salt = buf.slice (0, mode.salt_size); buf = buf.slice (mode.salt_size); // eslint-disable-next-line no-sync const key = crypto.pbkdf2Sync ( Buffer.from (pass), salt, mode.iterations, mode.key_size, mode.hash ); const nonce = buf.slice (0, mode.nonce_size); buf = buf.slice (mode.nonce_size); const cipher = crypto.createDecipheriv (mode.algorithm, key, nonce); return Buffer.concat ([ cipher.update (buf), cipher.final () ]) .toString (); } catch (e) { if (rethrow) throw e; } return null; } module.exports = { checksum, create_salt, decode_signed, decrypt_aes, encrypt_aes, encryption_mode_cbc_128, encryption_mode_cbc_256, encryption_mode_cbc_256_quick, get_signature_info, hash_sha512, random_hex, random_string, sign_object, verify_signature, verify_signature_get_info };