UNPKG

@synotech/utils

Version:

a collection of utilities for internal use

760 lines (713 loc) 19.8 kB
import { createCipheriv, createDecipheriv, createHmac, createPublicKey, createSign, createVerify, generateKeyPairSync, pbkdf2Sync, randomBytes, } from 'crypto'; import jwt from 'jsonwebtoken'; import { trace } from 'node:console'; interface CryptographyOptions { keys?: string[]; encryptKey?: string; encryptKeySingle?: string; } interface RandomOptions { length?: number; useLowerCase?: boolean; useUpperCase?: boolean; useNumbers?: boolean; useSpecial?: boolean; useHex?: boolean; } type KeyStrength = | 'decent_pw' | 'strong_pw' | 'ft_knox_pw' | 'ci_key' | '160_wpa' | '504_wpa' | '64_wep' | '128_wep' | '152_wep' | '256_wep'; /** * The Cryptography class provides methods for encryption, decryption, signing, and verification of data using various cryptographic algorithms. * * @class Cryptography * * @example * const cryptography = new Cryptography({ * keys: ['key1', 'key2', 'key3'], * encryptKey: '', * encryptKeySingle: '', * }) * * const encryptedData = cryptography.encrypt('Hello, World!') * const decryptedData = cryptography.decrypt(encryptedData) * * const signature = cryptography.signature() * const isVerified = cryptography.signatureVerify(signature) * * // creates a KrugerGold Token Address * const address = cryptography.address() * * const encodedData = cryptography.base64Encode('Hello, World!') * const decodedData = cryptography.base64Decode(encodedData) * const isEncodedOrNot = cryptography.isBase64Encoded(encodedData) * * cryptography.get('strong_pw') // returns i=SQ_qa3W[<RxoM * cryptography.random({ length: 20, useLowerCase: true, useUpperCase: true, useNumbers: true, useSpecial: true, useHex: false }) // returns *ajz:74,*ak0 * */ class Cryptography { ALG: any; KEYS: { encryptKey: string; encryptKeySingle: string }; keys: string[]; private lowerCase: string = 'abcdefghijklmnopqrstuvwxyz'; private upperCase: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; private numbers: string = '1234567890'; private special: string = '`~!@#$%^&*()-=_+[]{}|;\\\':",./<>?'; private hex: string = '123456789ABCDEF'; /** * Initializes a new instance of the Cryptography class. * @param keys - An array of keys used for encryption and decryption. */ constructor(options: CryptographyOptions) { const { keys, encryptKey, encryptKeySingle } = options; this.keys = keys || [ `D5uBt7z18on1Z1I2`, `fqIlGkitP9Ik7xmp`, `tttiKz8PaaExA6gZ`, ]; this.KEYS = { encryptKey: encryptKey ?? (process.env.ENCRYPTION_TWO_WAY_KEY as string), encryptKeySingle: encryptKeySingle ?? (process.env.ENCRYPTION_ONE_WAY_KEY as string), }; this.ALG = { /** * GCM is an authenticated encryption mode that * not only provides confidentiality but also * provides integrity in a secured way. */ NODE_CIPHER: 'aes-256-gcm', /** * 128 bit auth tag is recommended for GCM. */ AUTH_TAG_BYTE_LEN: 16, /** * NIST recommends 96 bits or 12 bytes IV for GCM * to promote interoperability, efficiency, and * simplicity of design. */ IV_BYTE_LEN: 12, /** * Note: 256 (in ALG name) is key size. * NODE size for AES is always 128. */ KEY_BYTE_LEN: 32, /** * To prevent rainbow table attacks. */ SALT_BYTE_LEN: 16, }; } private randomizer(): number { return randomBytes(1).readUInt8(0) / 255; } /** * @method random * The Random module class provides methods for generating random strings. * * @example * cryptography.random({ length: 20, useLowerCase: true, useUpperCase: true, useNumbers: true, useSpecial: true, useHex: false }) // returns *ajz:74,*ak0 * * // Decent Passwords - Good for nothing really, public accounts and other non-critical things. * cryptography.get('decent_pw') // returns rXjdx36oro * * // Strong Passwords - Robust enough to keep your web hosting account secure. * cryptography.get('strong_pw') // returns i=SQ_qa3W[<RxoM * * // Fort Knox Passwords - Secure enough for almost anything, like root or administrator passwords. * cryptography.get('ft_knox_pw') // returns P}U%H\OOYAYb;wc"3hgI,3Lz[gd-z] * * // Encryption Keys - Can be used for any other 256-bit key requirement. * cryptography.get('ci_key') // returns CeXHpM3nDgzdv0o3AkMCs3OuxzepLGW8 * * // 160-bit WPA Key * cryptography.get('160_wpa') // returns oHI#gR8z#h7BS>cZ!zH( * * // 504-bit WPA Key * cryptography.get('504_wpa') // returns <os[g`s}u06jqt"Ea]t11,HsI[UipHD)%F";:9RhJ@kTU8GknLpMAXtoCzsJjT` * * // 64-bit WEP Keys * cryptography.get('64_wep') // returns 8911B * * // 128-bit WEP Keys * cryptography.get('128_wep') // returns 9F4E4F933BCCC * * // 152-bit WEP Keys * cryptography.get('152_wep') // returns 695E1EE96E483961 * * // 256-bit WEP Keys * cryptography.get('256_wep') // returns AC7E866246BA6B71BF5D88A6861AB * */ public random(options: RandomOptions): string { const { length = 32, useLowerCase = true, useUpperCase = true, useNumbers = true, useSpecial = false, useHex = false, } = options; let chars = ''; let key = ''; if (useLowerCase) chars += this.lowerCase; if (useUpperCase) chars += this.upperCase; if (useNumbers) chars += this.numbers; if (useSpecial) chars += this.special; if (useHex) chars += this.hex; for (let i = 0; i < length; i++) { key += chars[Math.floor(this.randomizer() * chars.length)]; } return key; } /** * * @method get * @param strength - The strength of the key to generate. decent_pw|strong_pw|ft_knox_pw|ci_key|160_wpa|504_wpa|64_wep|128_wep|152_wep|256_wep */ public get(strength: KeyStrength): string { switch (strength) { case 'decent_pw': return this.random({ length: 10, useLowerCase: true, useUpperCase: true, useNumbers: true, useSpecial: false, useHex: false, }); case 'strong_pw': return this.random({ length: 15, useLowerCase: true, useUpperCase: true, useNumbers: true, useSpecial: true, useHex: false, }); case 'ft_knox_pw': return this.random({ length: 30, useLowerCase: true, useUpperCase: true, useNumbers: true, useSpecial: true, useHex: false, }); case 'ci_key': return this.random({ length: 32, useLowerCase: true, useUpperCase: true, useNumbers: true, useSpecial: false, useHex: false, }); case '160_wpa': return this.random({ length: 20, useLowerCase: true, useUpperCase: true, useNumbers: true, useSpecial: true, useHex: false, }); case '504_wpa': return this.random({ length: 63, useLowerCase: true, useUpperCase: true, useNumbers: true, useSpecial: true, useHex: false, }); case '64_wep': return this.random({ length: 5, useLowerCase: false, useUpperCase: false, useNumbers: false, useSpecial: false, useHex: true, }); case '128_wep': return this.random({ length: 13, useLowerCase: false, useUpperCase: false, useNumbers: false, useSpecial: false, useHex: true, }); case '152_wep': return this.random({ length: 16, useLowerCase: false, useUpperCase: false, useNumbers: false, useSpecial: false, useHex: true, }); case '256_wep': return this.random({ length: 29, useLowerCase: false, useUpperCase: false, useNumbers: false, useSpecial: false, useHex: true, }); default: throw new Error(`No such strength`); } } /** * @method salt * Generates a random salt value. * @returns The generated salt value as a hexadecimal string. * * @example * const salt = cryptography.salt() * console.log(salt) * // Output: * // 5eb63bbbe01eeed093cb22bb8f5acdc3 */ salt() { return randomBytes(this.ALG.SALT_BYTE_LEN).toString('hex'); } /** * @method iv * Generates a random initialization vector (IV). * @returns The generated IV as a Buffer. */ iv() { return randomBytes(this.ALG.AUTH_TAG_BYTE_LEN); } /** * @method signature * Generates a signature using the encryption keys. * @returns The generated signature as a string. * * @example * const signature = cryptography.signature() * console.log(signature) * // Output: * // 6a3a4b5c6d7e8f9a */ signature() { return this.encrypt(this.getKeys(2).join(' ')); } /** * @method signatureVerify * Verifies a signature against the encryption keys. * @param signature - The signature to verify. * @returns True if the signature is valid, false otherwise. * @throws An error if the signature contains unknown identifiers. * * @example * const signature = '6a3a4b5c6d7e8f9a' * const isValid = cryptography.signatureVerify(signature) * console.log(isValid) * // Output: * // true */ signatureVerify(signature: string) { const signatures = this.decrypt(signature).split(' '); if (signatures.every((value) => this.keys.includes(value))) { return true; } else { throw `Unknown identifier`; } } /** * @method address * Generates a random crypto address. * @returns The generated address as a string. * @throws An error if an error occurs during address generation. * * @example * const address = cryptography.address() * console.log(address) * // Output: * // kK5HCVlAmmEnnlKh6wVX9TiJ6YGI7FCl */ address() { try { const address = this.random({ length: this.ALG.KEY_BYTE_LEN - 1, }); return `k${address}`; } catch (error) { trace(error); throw error; } } /** * @method getKeys * Generates random keys from the provided key array. * @param count - The number of keys to generate. Defaults to 12. * @returns An array of randomly generated keys. */ getKeys = (count?: number) => { count = count ?? 12; const random = []; for (let i = 0; i < count; i++) { const randomIndex = Math.floor(Math.random() * this.keys.length); const randomWord = this.keys[randomIndex]; // @ts-ignore random.push(randomWord); } return random; }; /** * @method encryptSingle * Encrypts data one way using an encryption key. * @param data - The data to encrypt. * @returns The encrypted data as a base64-encoded string. * * @example * const crypto = new Cryptography(['key1', 'key2', 'key3'], 'encryptKey', 'encryptKeySingle') * const data = 'Hello, World!' * const encryptedData = cryptography.encryptSingle(data) * console.log(encryptedData) * // Output: * // 5eb63bbbe01eeed093cb22bb8f5acdc3 */ encryptSingle(data: any) { try { const signature = createHmac(`sha256`, this.KEYS.encryptKeySingle) .update(data) .digest(`hex`); const b64Buffer = Buffer.from(signature); return b64Buffer.toString(`base64`); } catch (error) { trace(error); return Buffer.alloc(16); } } /** * @method encrypt * Encrypts text two way using the encryption keys. * @param text - The text to encrypt. * @returns The encrypted text as a string. * @throws An error if an error occurs during encryption. * * @example * const text = 'Hello, World!' * const encryptedText = cryptography.encrypt(text) * console.log(encryptedText) * // Output: * // 6a3a4b5c6d7e8f9a:6a3a4b5c6d7e8f9a:6a3a4b5c6d7e8f9a */ encrypt(text: any) { try { const iv = randomBytes(this.ALG.AUTH_TAG_BYTE_LEN); const cipher = createCipheriv( this.ALG.NODE_CIPHER, this.KEYS.encryptKey, iv ); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); const tag = cipher.getAuthTag(); return `${iv.toString('hex')}:${encrypted}:${tag.toString('hex')}`; } catch (error) { trace(error); throw error; } } /** * @method decrypt * Decrypts encrypted text using the encryption keys. * @param encryptedText - The encrypted text to decrypt. * @returns The decrypted text as a string. * @throws An error if an error occurs during decryption. * * @example * const encryptedText = '6a3a4b5c6d7e8f9a:6a3a4b5c6d7e8f9a:6a3a4b5c6d7e8f9a' * const decryptedText = cryptography.decrypt(encryptedText) * console.log(decryptedText) * // Output: * // Hello, World! */ decrypt(encryptedText: any) { try { const [ivHex, encryptedHex, tagHex] = encryptedText.split(':'); const iv = Buffer.from(ivHex, 'hex'); const encrypted = Buffer.from(encryptedHex, 'hex'); const tag = Buffer.from(tagHex, 'hex'); const decipher = createDecipheriv( this.ALG.NODE_CIPHER, this.KEYS.encryptKey, iv ); decipher.setAuthTag(tag); let decrypted = decipher.update(encrypted, undefined, 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (error) { trace(error); throw error; } } /** * @method password * Generates a password hash using the provided password and salt. * @param password - The password to hash. * @param salt - The salt value to use for hashing. * @returns The hashed password as a hexadecimal string. * @throws An error if an error occurs during password hashing. * * @example * const password = 'myPassword' * const salt = cryptography.salt() * const hashedPassword = cryptography.password(password, salt) * console.log(hashedPassword) * // Output: * // 5ebe2294ecd0e0f08eab7690d2a6ee69 */ password(password: string, salt: string) { try { const pbkdf = pbkdf2Sync(password, salt, 10000, 256, `sha256`); return pbkdf.toString('hex'); } catch (error) { trace(error); throw error; } } /** * @method jwtIssue * Issues a JSON Web Token (JWT) with the provided payload and expiry. * @param payload - The payload to include in the JWT. * @param expiry - The expiry time for the JWT. * @returns The issued JWT as a string. * @throws An error if an error occurs during JWT issuance. * * @example * const payload = { * sub: '1234567890', * name: 'John Doe', * iat: 1516239022 * } * * const expiry = '1h' * const token = cryptography.jwtIssue(payload, expiry) * console.log(token) * // Output: * // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c */ jwtIssue(payload: any = {}, expiry: string) { try { return jwt.sign( { ...payload, }, this.KEYS.encryptKeySingle, { expiresIn: expiry, } ); } catch (error) { trace(error); throw error; } } /** * @method jwtVerify * Verifies a JSON Web Token (JWT) and returns the decoded payload. * @param token - The JWT to verify. * @returns The decoded payload if the JWT is valid. * @throws An error if the JWT is invalid or an error occurs during verification. * * @example * const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' * const payload = cryptography.jwtVerify(token) * console.log(payload) * // Output: * // { * // sub: '1234567890', * // name: 'John Doe', * // iat: 1516239022 * // } */ jwtVerify(token: string) { try { return jwt.verify(token, this.KEYS.encryptKeySingle); } catch (error) { trace(error); throw error; } } /** * @method publicKey * Generates a private key for asymmetric encryption. * @returns The generated private key as a PEM-encoded string. * @throws An error if an error occurs during private key generation. * * @example * const privateKey = cryptography.privateKey() * console.log(privateKey) * // Output: * // -----BEGIN PRIVATE KEY----- * // MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBA4GCAWqjggFMMIIB * // ... * // -----END PRIVATE KEY----- */ privateKey() { try { const { privateKey } = generateKeyPairSync(`ec`, { namedCurve: 'secp521r1', }); return privateKey.export({ type: 'pkcs8', format: 'pem' }); } catch (error) { trace(error); throw error; } } /** * @method publicKey * * Generates a public key from the provided private key. * @param privateKey - The private key to generate the public key from. * @returns The generated public key as a PEM-encoded string. * * @example * const privateKey = `-----BEGIN PRIVATE KEY----- * MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDZ1Ck6vJQK0J5T * ... * -----END PRIVATE KEY-----` * * const publicKey = cryptography.publicKey(privateKey) * console.log(publicKey) * // Output: * // -----BEGIN PUBLIC KEY----- * // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2dQpOryUCtCeUz8vZ6zB * // ... * // -----END PUBLIC KEY----- */ publicKey(privateKey: string) { const publicKey = createPublicKey(privateKey); return publicKey.export({ type: 'spki', format: 'pem' }); } /** * @method publicKeyVerify * Verifies the authenticity of a public key using the provided private and public keys. * @param privateKey - The private key used to sign the data. * @param publicKey - The public key used to verify the signature. * @returns True if the public key is authentic, false otherwise. * @throws An error if the public key fails to authenticate. * * @example * const privateKey = `-----BEGIN PRIVATE KEY----- * MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDZ1Ck6vJQK0J5T * ... * -----END PRIVATE KEY-----` * const publicKey = `-----BEGIN PUBLIC KEY----- * MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2dQpOryUCtCeUz8vZ6zB * ... * -----END PUBLIC KEY-----` * const isAuthentic = cryptography.publicKeyVerify({ privateKey, publicKey }) * console.log(isAuthentic) * // Output: * // true */ publicKeyVerify({ privateKey, publicKey, }: { privateKey: string; publicKey: string; }) { try { const data = this.KEYS.encryptKey; const sign = createSign(`RSA-SHA256`); sign.update(data); const signature = sign.sign(privateKey, `base64`); const verify = createVerify(`RSA-SHA256`); verify.update(data); const isValid = verify.verify(publicKey, signature, `base64`); if (isValid) { return true; } else { throw `Failed to authenticate the public key`; } } catch (error) { trace(error); throw error; } } /** * @method isBase64Encoded * Checks if a string is base64 encoded. * @param string - The string to check. * @returns True if the string is base64 encoded, false otherwise. * * @example * const encodedString = 'SGVsbG8sIFdvcmxkIQ==' * const isEncoded = cryptography.isBase64Encoded(encodedString) * console.log(isEncoded) * // Output: * // true */ isBase64Encoded(string: string) { try { return Buffer.from(string, 'base64').toString('base64') === string; } catch (err) { return false; } } /** * @method base64Encode * Encodes data as base64. * @param data - The data to encode. * @returns The base64-encoded string. * * @example * const data = 'Hello, World!' * const encodedData = cryptography.base64Encode(data) * console.log(encodedData) * // Output: * // SGVsbG8sIFdvcmxkIQ== */ base64Encode(data: string) { return Buffer.from(data).toString('base64'); } /** * @method base64Decode * Decodes a base64-encoded string. * @param encodedString - The base64-encoded string to decode. * @returns The decoded string. * * @example * const encodedString = 'SGVsbG8sIFdvcmxkIQ==' * const decodedString = cryptography.base64Decode(encodedString) * console.log(decodedString) * // Output: * // Hello, World! */ base64Decode(encodedString: string) { return Buffer.from(encodedString, 'base64').toString('utf-8'); } } export { Cryptography };