|
| 1 | +/** |
| 2 | + * @author Victor Giovanni Beltrán Rodríguez |
| 3 | + * @file Manages `CryptoDriver` class related to cipher and decipher of data. |
| 4 | + */ |
| 5 | + |
| 6 | +// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| 7 | +// » IMPORT NATIVE NODE MODULES |
| 8 | +const crypto = require('crypto'); |
| 9 | + |
| 10 | +// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| 11 | +/** |
| 12 | + * Type definition for the cipher options object. |
| 13 | + * |
| 14 | + * @private |
| 15 | + * @typedef {crypto.CipherGCMOptions} CipherGCMOptions |
| 16 | + */ |
| 17 | + |
| 18 | +/** |
| 19 | + * Defines the structure of an object that holds error messages thrown by the |
| 20 | + * `CryptoDriver` class. |
| 21 | + * |
| 22 | + * @private |
| 23 | + * @typedef {object} CryptoDriverErrors |
| 24 | + * @property {string} LENGTH_KEY - Error message thrown when the `key` value is not 32 characters long. |
| 25 | + * @property {string} UNDEFINED_KEY - Error message thrown when the `key` value is not provided. |
| 26 | + * @property {string} TYPE_KEY - Error message thrown when the `key` value is not a string. |
| 27 | + * @property {string} UNDEFINED_DATA - Error message thrown when the `data` value is not provided. |
| 28 | + * @property {string} TYPE_DATA - Error message thrown when the `data` value is not a string. |
| 29 | + * @property {string} UNDEFINED_ENCRYPTED - Error message thrown when the `encrypted` value is not provided. |
| 30 | + * @property {string} TYPE_ENCRYPTED - Error message thrown when the `encrypted` value is not a string. |
| 31 | + */ |
| 32 | + |
| 33 | +// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| 34 | +/** |
| 35 | + * Algorithm used for encryption and decryption. |
| 36 | + * |
| 37 | + * @private |
| 38 | + * @constant ALGORITHM |
| 39 | + * @type {string} |
| 40 | + */ |
| 41 | +const ALGORITHM = 'aes-256-gcm'; |
| 42 | + |
| 43 | +/** |
| 44 | + * Length in bytes of the GCM auth tag used for encryption and decryption. |
| 45 | + * |
| 46 | + * @private |
| 47 | + * @constant TAG_BYTE_LENGTH |
| 48 | + * @type {number} |
| 49 | + */ |
| 50 | +const TAG_BYTE_LENGTH = 16; |
| 51 | + |
| 52 | +/** |
| 53 | + * Length in bytes of the initialization vector (IV) used for encryption and |
| 54 | + * decryption. |
| 55 | + * |
| 56 | + * @private |
| 57 | + * @constant IV_BYTE_LENGTH |
| 58 | + * @type {number} |
| 59 | + */ |
| 60 | +const IV_BYTE_LENGTH = 12; |
| 61 | + |
| 62 | +/** |
| 63 | + * Options object for the GCM auth tag length. |
| 64 | + * |
| 65 | + * @private |
| 66 | + * @constant IV_OPTIONS |
| 67 | + * @type {CipherGCMOptions} |
| 68 | + */ |
| 69 | +const IV_OPTIONS = { |
| 70 | + authTagLength: TAG_BYTE_LENGTH, |
| 71 | +}; |
| 72 | + |
| 73 | +/** |
| 74 | + * Holds error messages thrown by the CryptoDriver class. |
| 75 | + * |
| 76 | + * @private |
| 77 | + * @constant ERRORS |
| 78 | + * @type {CryptoDriverErrors} |
| 79 | + */ |
| 80 | +const ERRORS = { |
| 81 | + LENGTH_KEY: 'The "Key" value must be 32 characters (256 bits).', |
| 82 | + UNDEFINED_KEY: 'The "key" value must be provided and must be a string.', |
| 83 | + TYPE_KEY: 'The "key" value must be of type string', |
| 84 | + UNDEFINED_DATA: 'The "data" value must be provided and must be a string.', |
| 85 | + TYPE_DATA: 'The "data" value must be of type string', |
| 86 | + UNDEFINED_ENCRYPTED: 'The "encrypted" value must be provided and must be a string.', |
| 87 | + TYPE_ENCRYPTED: 'The "encrypted" value must be of type string', |
| 88 | +}; |
| 89 | + |
| 90 | +// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| 91 | +/** |
| 92 | + * The CryptoDriver class contain methods to encrypt and decrypt data. |
| 93 | + * |
| 94 | + * @class |
| 95 | + * @classdesc This class uses the AES-256-GCM encryption algorithm with a |
| 96 | + * 256-bit key, which is considered one of the most secure symmetric encryption |
| 97 | + * algorithms available. |
| 98 | + * @see {@link https://en.wikipedia.org/wiki/Advanced_Encryption_Standard Advanced Encryption Standard} |
| 99 | + * @see {@link https://en.wikipedia.org/wiki/Galois/Counter_Mode Galois/Counter Mode} |
| 100 | + * |
| 101 | + * @author Victor Giovanni Beltrán Rodríguez |
| 102 | + * @version 1.0.0 |
| 103 | + */ |
| 104 | +class CryptoDriver { |
| 105 | + /** |
| 106 | + * Creates an instance of CryptoDriver, the `contructor` method require |
| 107 | + * the param `key` must be a string of 32 characters. |
| 108 | + * |
| 109 | + * @param {string} key - The key used for encryption and decryption. |
| 110 | + * @memberof CryptoDriver |
| 111 | + * @throws {ReferenceError} If `key` value is `undefined`. |
| 112 | + * @throws {TypeError} If `key` type is other than string. |
| 113 | + * @throws {RangeError} If `key` type length is other than 32. |
| 114 | + * @example |
| 115 | + * ```js |
| 116 | + * const crypto = new CryptoDriver('d6F3Efeqd6F3Efeqd6F3Efeqd6F3Efeq'); |
| 117 | + *``` |
| 118 | + */ |
| 119 | + constructor(key) { |
| 120 | + if (key === undefined) throw new ReferenceError(ERRORS.UNDEFINED_KEY); |
| 121 | + if (typeof key !== 'string') throw new TypeError(ERRORS.TYPE_KEY); |
| 122 | + if (key.length !== 32) throw new RangeError(ERRORS.LENGTH_KEY); |
| 123 | + /** |
| 124 | + * Key used for encryption and decryption methods, must be of type string |
| 125 | + * with 32 characters. |
| 126 | + * |
| 127 | + * @name key |
| 128 | + * @memberof CryptoDriver# |
| 129 | + * @type {string} |
| 130 | + * @readonly |
| 131 | + */ |
| 132 | + Object.defineProperty(this, 'key', { |
| 133 | + value: key, |
| 134 | + writable: false, |
| 135 | + enumerable: false, |
| 136 | + configurable: false, |
| 137 | + }); |
| 138 | + } |
| 139 | + |
| 140 | + /** |
| 141 | + * Methods to encrypt data, encrypts a string using the AES-256-GCM |
| 142 | + * encryption algorithm with the key specified in the constructor. |
| 143 | + * |
| 144 | + * @param {string} data - Data to encrypt. |
| 145 | + * @returns {string} The encrypted data. |
| 146 | + * @memberof CryptoDriver |
| 147 | + * @throws {ReferenceError} If `data` value is `undefined`. |
| 148 | + * @throws {TypeError} If `data` type is other than string. |
| 149 | + * @example |
| 150 | + * ```js |
| 151 | + * const crypto = new CryptoDriver('d6F3Efeqd6F3Efeqd6F3Efeqd6F3Efeq'); |
| 152 | + * const encrypted = crypto.encrypt('Hello world'); |
| 153 | + * // Output like a e5be8c02e8d478b0ab9...8c28e6828010a5789a446e781f36d0 |
| 154 | + *``` |
| 155 | + */ |
| 156 | + encrypt(data) { |
| 157 | + if (data === undefined) throw new ReferenceError(ERRORS.UNDEFINED_DATA); |
| 158 | + if (typeof data !== 'string') throw new TypeError(ERRORS.TYPE_DATA); |
| 159 | + const iv = crypto.randomBytes(IV_BYTE_LENGTH); |
| 160 | + const cipher = crypto.createCipheriv(ALGORITHM, this.key, iv, IV_OPTIONS); |
| 161 | + const raw = Buffer.from(data, 'utf-8'); |
| 162 | + const encrypted = Buffer.concat([iv, cipher.update(raw), cipher.final(), cipher.getAuthTag()]); |
| 163 | + return encrypted.toString('hex'); |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * Methods to decrypt data. |
| 168 | + * |
| 169 | + * @param {string} encrypted - Encrypted data. |
| 170 | + * @returns {string} Decrypted data. |
| 171 | + * @memberof CryptoDriver |
| 172 | + * @throws {ReferenceError} If `encrypted` value is `undefined`. |
| 173 | + * @throws {TypeError} If `encrypted` type is other than string. |
| 174 | + * @example |
| 175 | + * ```js |
| 176 | + * // Output like a e5be8c02e8d478b0ab9...8c28e6828010a5789a446e781f36d0 |
| 177 | + * const encrypted = cryptoDriver.encrypt('Hello world'); |
| 178 | + * const decrypted = cryptoDriver.decrypt(encrypted); // Hello world |
| 179 | + *``` |
| 180 | + */ |
| 181 | + decrypt(encrypted) { |
| 182 | + if (encrypted === undefined) throw new ReferenceError(ERRORS.UNDEFINED_ENCRYPTED); |
| 183 | + if (typeof encrypted !== 'string') throw new TypeError(ERRORS.TYPE_ENCRYPTED); |
| 184 | + const cipher = Buffer.from(encrypted, 'hex'); |
| 185 | + const iv = cipher.subarray(0, IV_BYTE_LENGTH); |
| 186 | + const data = cipher.subarray(IV_BYTE_LENGTH, cipher.length - TAG_BYTE_LENGTH); |
| 187 | + const authTag = cipher.subarray(data.length + IV_BYTE_LENGTH); |
| 188 | + const decipher = crypto.createDecipheriv(ALGORITHM, this.key, iv, IV_OPTIONS); |
| 189 | + decipher.setAuthTag(authTag); |
| 190 | + const decrypted = Buffer.concat([decipher.update(data), decipher.final()]); |
| 191 | + return decrypted.toString('utf-8'); |
| 192 | + } |
| 193 | +} |
| 194 | + |
| 195 | +// ━━ EXPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ |
| 196 | +module.exports = CryptoDriver; |
0 commit comments