/* * Copyright (C) Sapphirecode - All Rights Reserved * Created by Timo Hocker , March 2020 */ /* eslint-disable no-magic-numbers */ 'use strict'; const crypto = require ('crypto'); const encoding = require ('@scode/encoding-helper'); const encryption = { algorithm: 'aes-256-gcm', nonce_size: 12, tag_size: 16, key_size: 16, hash: 'sha256', salt_size: 16, iterations: 32767 }; /** * creates a random string * * @param {number} len string length default: 6 * @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') .replace (/[=]+$/u, '') .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} key_info key identifier * @returns {string} signed object */ function sign_object (obj, key, key_info = null) { const payload = { iat: Date.now (), key_info, obj }; 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 contents * * @param {string} str string to verify * @param {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 dec = decodeURIComponent (str) .split ('.'); const json = JSON.parse (encoding.to_utf8 (dec[0], 'base64')); const token = encoding.to_hex (dec[1], 'base64'); const verify_token = hash_sha512 (dec[0], key); if (token !== verify_token) return null; const time = Date.now () - json.iat; if (timeout !== 0 && time > timeout) return null; return json.obj; } /** * get a signed object info and data * * @param {string} str string to decode * @returns {any} data */ function get_signature_info (str) { 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) { return get_signature_info (str).obj; } /** * 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 * @returns {string} encrypted */ function encrypt_aes (text, pass) { const salt = crypto.randomBytes (16); // eslint-disable-next-line no-sync const key = crypto.pbkdf2Sync ( Buffer.from (pass, 'utf-8'), salt, 32767, 32, 'sha256' ); const nonce = crypto.randomBytes (12); const cipher = crypto.createCipheriv ('aes-256-gcm', key, nonce); return Buffer.concat ([ salt, nonce, cipher.update (Buffer.from (text)), cipher.final (), cipher.getAuthTag () ]) .toString ('base64'); } /** * decrypt an aes string * * @param {string} ciphertext encrypted text * @param {string} pass password * @returns {string} plaintext */ function decrypt_aes (ciphertext, pass) { const buf = Buffer.from (ciphertext, 'base64'); const salt = buf.slice (0, encryption.salt_size); // eslint-disable-next-line no-sync const key = crypto.pbkdf2Sync ( Buffer.from (pass, 'utf-8'), salt, encryption.iterations, encryption.key_size, encryption.hash ); const nonce = buf.slice (encryption.salt_size, encryption.nonce_size); const enc = buf.slice ( encryption.salt_size + encryption.nonce_size, buf.length - encryption.salt_size - encryption.tag_size ); const tag = buf.slice ( encryption.salt_size + encryption.nonce_size + enc.length ); const cipher = crypto.createDecipheriv (encryption.algorithm, key, nonce); cipher.setAuthTag (tag); return Buffer.concat ([ cipher.update (enc), cipher.final () ]) .toString ('utf-8'); } module.exports = { checksum, create_salt, decode_signed, decrypt_aes, encrypt_aes, get_signature_info, hash_sha512, random_hex, random_string, sign_object, verify_signature };