| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646 |
- /**
- * @node crypto
- * @name Crypto
- * @category security
- * @version 1.1.0
- * @description Hash data, generate HMACs, encrypt/decrypt data using AES or RSA, and generate random values
- * @icon lock
- */
- const configSchema = {
- type: 'object',
- properties: {
- operation: {
- type: 'string',
- title: 'Operation',
- enum: ['hash', 'hmac', 'random', 'encrypt', 'decrypt'],
- enumLabels: ['Hash', 'HMAC', 'Random Generation', 'Encrypt', 'Decrypt'],
- default: 'hash',
- description: 'Cryptographic operation to perform'
- },
- algorithm: {
- type: 'string',
- title: 'Algorithm',
- enum: ['md5', 'sha1', 'sha256', 'sha384', 'sha512'],
- enumLabels: ['MD5', 'SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'],
- default: 'sha256',
- description: 'Hash algorithm to use',
- showWhen: { field: 'operation', value: ['hash', 'hmac'] }
- },
- outputFormat: {
- type: 'string',
- title: 'Output Format',
- enum: ['hex', 'base64'],
- enumLabels: ['Hexadecimal', 'Base64'],
- default: 'hex',
- description: 'Format of the output hash or HMAC',
- showWhen: { field: 'operation', value: ['hash', 'hmac'] }
- },
- secretKey: {
- type: 'string',
- title: 'Secret Key',
- description: 'Secret key for HMAC generation. Supports {{variable}} interpolation.',
- showWhen: { field: 'operation', value: 'hmac' }
- },
- randomType: {
- type: 'string',
- title: 'Random Type',
- enum: ['uuid', 'hex', 'alphanumeric', 'password'],
- enumLabels: ['UUID v4', 'Hex Token', 'Alphanumeric String', 'Secure Password'],
- default: 'uuid',
- description: 'Type of random value to generate',
- showWhen: { field: 'operation', value: 'random' }
- },
- randomLength: {
- type: 'number',
- title: 'Length',
- default: 32,
- minimum: 1,
- maximum: 1024,
- description: 'Length of the generated value (1-1024). For hex, this is number of bytes (output will be 2x characters).',
- showWhen: { field: 'operation', value: 'random' }
- },
- passwordUppercase: {
- type: 'boolean',
- title: 'Include Uppercase',
- default: true,
- description: 'Include uppercase letters (A-Z)',
- showWhen: { field: 'randomType', value: 'password' }
- },
- passwordLowercase: {
- type: 'boolean',
- title: 'Include Lowercase',
- default: true,
- description: 'Include lowercase letters (a-z)',
- showWhen: { field: 'randomType', value: 'password' }
- },
- passwordNumbers: {
- type: 'boolean',
- title: 'Include Numbers',
- default: true,
- description: 'Include numbers (0-9)',
- showWhen: { field: 'randomType', value: 'password' }
- },
- passwordSymbols: {
- type: 'boolean',
- title: 'Include Symbols',
- default: true,
- description: 'Include symbols (!@#$%^&*()_+-=[]{}|;:,.<>?)',
- showWhen: { field: 'randomType', value: 'password' }
- },
- encryptionAlgorithm: {
- type: 'string',
- title: 'Encryption Algorithm',
- enum: ['aes-256-gcm', 'aes-256-cbc', 'rsa'],
- enumLabels: ['AES-256-GCM (Recommended)', 'AES-256-CBC', 'RSA'],
- default: 'aes-256-gcm',
- description: 'Encryption algorithm to use',
- showWhen: { field: 'operation', value: ['encrypt', 'decrypt'] }
- },
- encryptionKey: {
- type: 'string',
- title: 'Encryption Key',
- description: 'For AES: Base64-encoded 256-bit key OR passphrase (set Use Passphrase to true). For RSA encrypt: PEM public key. Supports {{variable}} interpolation.',
- showWhen: { field: 'operation', value: 'encrypt' }
- },
- decryptionKey: {
- type: 'string',
- title: 'Decryption Key',
- description: 'For AES: Base64-encoded 256-bit key OR passphrase (set Use Passphrase to true). For RSA decrypt: PEM private key. Supports {{variable}} interpolation.',
- showWhen: { field: 'operation', value: 'decrypt' }
- },
- usePassphrase: {
- type: 'boolean',
- title: 'Use Passphrase',
- default: false,
- description: 'If true, the key is treated as a passphrase and a 256-bit key is derived using PBKDF2',
- showWhen: { field: 'encryptionAlgorithm', value: ['aes-256-gcm', 'aes-256-cbc'] }
- },
- iv: {
- type: 'string',
- title: 'IV (Initialization Vector)',
- description: 'Optional. Base64-encoded IV. If not provided, a random IV will be generated for encryption. Required for decryption.',
- showWhen: { field: 'encryptionAlgorithm', value: ['aes-256-gcm', 'aes-256-cbc'] }
- },
- authTag: {
- type: 'string',
- title: 'Authentication Tag',
- description: 'Required for AES-GCM decryption. Base64-encoded authentication tag.',
- showWhen: { field: 'operation', value: 'decrypt' }
- },
- inputFormat: {
- type: 'string',
- title: 'Input Format',
- enum: ['string', 'base64'],
- enumLabels: ['String (UTF-8)', 'Base64'],
- default: 'string',
- description: 'Format of the input data for encryption, or ciphertext format for decryption',
- showWhen: { field: 'operation', value: ['encrypt', 'decrypt'] }
- },
- outputDataFormat: {
- type: 'string',
- title: 'Output Format',
- enum: ['base64', 'string'],
- enumLabels: ['Base64', 'String (UTF-8)'],
- default: 'base64',
- description: 'Format of the encrypted ciphertext output, or decrypted plaintext output',
- showWhen: { field: 'operation', value: ['encrypt', 'decrypt'] }
- }
- },
- required: ['operation']
- };
- const inputSchema = {
- type: 'object',
- properties: {
- data: {
- type: 'any',
- description: 'Data to process. For hash/hmac: string or will be JSON stringified. For encrypt: plaintext. For decrypt: ciphertext.'
- },
- secretKey: {
- type: 'string',
- description: 'Override secret key for HMAC from input'
- },
- encryptionKey: {
- type: 'string',
- description: 'Override encryption key from input (for encrypt operation)'
- },
- decryptionKey: {
- type: 'string',
- description: 'Override decryption key from input (for decrypt operation)'
- },
- iv: {
- type: 'string',
- description: 'Override IV from input (for AES decrypt)'
- },
- authTag: {
- type: 'string',
- description: 'Override auth tag from input (for AES-GCM decrypt)'
- },
- publicKey: {
- type: 'string',
- description: 'RSA public key in PEM format (for RSA encrypt)'
- },
- privateKey: {
- type: 'string',
- description: 'RSA private key in PEM format (for RSA decrypt)'
- }
- }
- };
- const outputSchema = {
- type: 'object',
- properties: {
- result: {
- type: 'string',
- description: 'The cryptographic operation result (hash, encrypted data, or decrypted data)'
- },
- algorithm: {
- type: 'string',
- description: 'Algorithm used for the operation'
- },
- operation: {
- type: 'string',
- description: 'Operation that was performed'
- },
- format: {
- type: 'string',
- description: 'Output format (hex or base64)'
- },
- randomType: {
- type: 'string',
- description: 'Type of random value generated (uuid, hex, alphanumeric, password)'
- },
- length: {
- type: 'number',
- description: 'Length of the generated random value'
- },
- inputLength: {
- type: 'number',
- description: 'Length of the input data in bytes'
- },
- iv: {
- type: 'string',
- description: 'Base64-encoded IV used for encryption (save this for decryption)'
- },
- authTag: {
- type: 'string',
- description: 'Base64-encoded authentication tag for AES-GCM (save this for decryption)'
- }
- }
- };
- function interpolate(template, data) {
- if (typeof template !== 'string') return template;
- return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
- const keys = key.trim().split('.');
- let value = data;
- for (const k of keys) {
- if (value && typeof value === 'object' && k in value) {
- value = value[k];
- } else {
- return match;
- }
- }
- return value !== undefined ? String(value) : match;
- });
- }
- function generateRandomString(length, charset) {
- const charsetLength = charset.length;
- let result = '';
- const bytesNeeded = length;
- const randomHex = smartbotic.crypto.randomBytes(bytesNeeded * 2);
- let pos = 0;
- while (result.length < length) {
- if (pos >= randomHex.length) {
- const moreBytes = smartbotic.crypto.randomBytes((length - result.length) * 2);
- pos = 0;
- for (let i = 0; i < moreBytes.length && result.length < length; i += 2) {
- const byte = parseInt(moreBytes.substr(i, 2), 16);
- const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength;
- if (byte < maxUnbiased) {
- result += charset[byte % charsetLength];
- }
- }
- } else {
- const byte = parseInt(randomHex.substr(pos, 2), 16);
- pos += 2;
- const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength;
- if (byte < maxUnbiased) {
- result += charset[byte % charsetLength];
- }
- }
- }
- return result;
- }
- async function execute(config, input, context) {
- const operation = config.operation || 'hash';
- if (operation === 'encrypt') {
- return executeEncrypt(config, input);
- }
- if (operation === 'decrypt') {
- return executeDecrypt(config, input);
- }
- if (operation === 'random') {
- const randomType = config.randomType || 'uuid';
- const length = config.randomLength || 32;
- if (randomType !== 'uuid' && (length < 1 || length > 1024)) {
- throw new Error('Length must be between 1 and 1024');
- }
- if (randomType === 'uuid') {
- const uuid = smartbotic.crypto.randomUUID();
- smartbotic.log.info('Generated random UUID v4');
- return {
- result: uuid,
- operation: 'random',
- randomType: 'uuid',
- length: 36
- };
- }
- if (randomType === 'hex') {
- const randomHex = smartbotic.crypto.randomBytes(length);
- smartbotic.log.info('Generated ' + length + '-byte hex token (' + randomHex.length + ' characters)');
- return {
- result: randomHex,
- operation: 'random',
- randomType: 'hex',
- length: randomHex.length
- };
- }
- if (randomType === 'alphanumeric') {
- const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
- const result = generateRandomString(length, charset);
- smartbotic.log.info('Generated ' + length + '-character alphanumeric string');
- return {
- result: result,
- operation: 'random',
- randomType: 'alphanumeric',
- length: length
- };
- }
- if (randomType === 'password') {
- const includeUppercase = config.passwordUppercase !== false;
- const includeLowercase = config.passwordLowercase !== false;
- const includeNumbers = config.passwordNumbers !== false;
- const includeSymbols = config.passwordSymbols !== false;
- let charset = '';
- if (includeUppercase) charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
- if (includeLowercase) charset += 'abcdefghijklmnopqrstuvwxyz';
- if (includeNumbers) charset += '0123456789';
- if (includeSymbols) charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';
- if (charset.length === 0) {
- throw new Error('At least one character set must be enabled for password generation');
- }
- const result = generateRandomString(length, charset);
- smartbotic.log.info('Generated ' + length + '-character secure password');
- return {
- result: result,
- operation: 'random',
- randomType: 'password',
- length: length
- };
- }
- throw new Error('Unknown random type: ' + randomType);
- }
- const inputData = input.data !== undefined ? input.data : input;
- let dataStr;
- if (typeof inputData === 'string') {
- dataStr = inputData;
- } else if (inputData === null || inputData === undefined) {
- throw new Error('No data provided for ' + operation + ' operation');
- } else {
- dataStr = JSON.stringify(inputData);
- }
- const algorithm = config.algorithm || 'sha256';
- const outputFormat = config.outputFormat || 'hex';
- if (operation === 'hash') {
- smartbotic.log.info('Hashing ' + dataStr.length + ' bytes with ' + algorithm.toUpperCase());
- const result = smartbotic.crypto.hash(dataStr, algorithm, outputFormat);
- return {
- result: result,
- algorithm: algorithm,
- operation: 'hash',
- format: outputFormat,
- inputLength: dataStr.length
- };
- }
- if (operation === 'hmac') {
- let secretKey = input.secretKey || config.secretKey;
- if (secretKey && typeof secretKey === 'string') {
- secretKey = interpolate(secretKey, input);
- }
- if (!secretKey) {
- throw new Error('Secret key is required for HMAC operation');
- }
- smartbotic.log.info('Generating HMAC-' + algorithm.toUpperCase() + ' for ' + dataStr.length + ' bytes');
- const result = smartbotic.crypto.hmac(dataStr, secretKey, algorithm, outputFormat);
- return {
- result: result,
- algorithm: algorithm,
- operation: 'hmac',
- format: outputFormat,
- inputLength: dataStr.length
- };
- }
- throw new Error('Unknown operation: ' + operation);
- }
- function executeEncrypt(config, input) {
- const encryptionAlgorithm = config.encryptionAlgorithm || 'aes-256-gcm';
- const inputFormat = config.inputFormat || 'string';
- let inputData = input.data !== undefined ? input.data : input;
- if (typeof inputData !== 'string') {
- if (inputData === null || inputData === undefined) {
- throw new Error('No data provided for encryption');
- }
- inputData = JSON.stringify(inputData);
- }
- let dataToEncrypt = inputData;
- if (inputFormat === 'base64') {
- dataToEncrypt = smartbotic.utils.base64Decode(inputData);
- if (!dataToEncrypt) {
- throw new Error('Invalid base64 input data');
- }
- }
- if (encryptionAlgorithm === 'rsa') {
- return executeRsaEncrypt(config, input, dataToEncrypt);
- }
- return executeAesEncrypt(config, input, dataToEncrypt, encryptionAlgorithm);
- }
- function executeAesEncrypt(config, input, dataToEncrypt, encryptionAlgorithm) {
- let key = input.encryptionKey || config.encryptionKey;
- if (key && typeof key === 'string') {
- key = interpolate(key, input);
- }
- if (!key) {
- throw new Error('Encryption key is required for AES encryption');
- }
- let iv = input.iv || config.iv;
- if (iv && typeof iv === 'string') {
- iv = interpolate(iv, input);
- }
- const usePassphrase = config.usePassphrase === true;
- const algorithm = encryptionAlgorithm === 'aes-256-cbc' ? 'cbc' : 'gcm';
- smartbotic.log.info('Encrypting ' + dataToEncrypt.length + ' bytes with AES-256-' + algorithm.toUpperCase());
- const result = smartbotic.crypto.aesEncrypt({
- data: dataToEncrypt,
- key: key,
- iv: iv || '',
- algorithm: algorithm,
- keyIsPassphrase: usePassphrase
- });
- if (result.error) {
- throw new Error('Encryption failed: ' + result.error);
- }
- const outputResult = {
- result: result.ciphertext,
- operation: 'encrypt',
- algorithm: encryptionAlgorithm,
- iv: result.iv,
- inputLength: dataToEncrypt.length
- };
- if (algorithm === 'gcm') {
- outputResult.authTag = result.authTag;
- }
- return outputResult;
- }
- function executeRsaEncrypt(config, input, dataToEncrypt) {
- let publicKey = input.publicKey || input.encryptionKey || config.encryptionKey;
- if (publicKey && typeof publicKey === 'string') {
- publicKey = interpolate(publicKey, input);
- }
- if (!publicKey) {
- throw new Error('Public key is required for RSA encryption');
- }
- if (!publicKey.includes('-----BEGIN')) {
- throw new Error('Public key must be in PEM format (starting with -----BEGIN PUBLIC KEY-----)');
- }
- smartbotic.log.info('Encrypting ' + dataToEncrypt.length + ' bytes with RSA');
- const result = smartbotic.crypto.rsaEncrypt({
- data: dataToEncrypt,
- publicKey: publicKey
- });
- if (result.error) {
- throw new Error('RSA encryption failed: ' + result.error);
- }
- return {
- result: result.ciphertext,
- operation: 'encrypt',
- algorithm: 'rsa',
- inputLength: dataToEncrypt.length
- };
- }
- function executeDecrypt(config, input) {
- const encryptionAlgorithm = config.encryptionAlgorithm || 'aes-256-gcm';
- const outputDataFormat = config.outputDataFormat || 'base64';
- let inputData = input.data !== undefined ? input.data : input;
- if (typeof inputData !== 'string') {
- if (inputData === null || inputData === undefined) {
- throw new Error('No data provided for decryption');
- }
- throw new Error('Ciphertext must be a base64-encoded string');
- }
- if (encryptionAlgorithm === 'rsa') {
- return executeRsaDecrypt(config, input, inputData, outputDataFormat);
- }
- return executeAesDecrypt(config, input, inputData, encryptionAlgorithm, outputDataFormat);
- }
- function executeAesDecrypt(config, input, ciphertext, encryptionAlgorithm, outputDataFormat) {
- let key = input.decryptionKey || config.decryptionKey;
- if (key && typeof key === 'string') {
- key = interpolate(key, input);
- }
- if (!key) {
- throw new Error('Decryption key is required for AES decryption');
- }
- let iv = input.iv || config.iv;
- if (iv && typeof iv === 'string') {
- iv = interpolate(iv, input);
- }
- if (!iv) {
- throw new Error('IV is required for AES decryption');
- }
- const usePassphrase = config.usePassphrase === true;
- const algorithm = encryptionAlgorithm === 'aes-256-cbc' ? 'cbc' : 'gcm';
- const decryptOptions = {
- ciphertext: ciphertext,
- key: key,
- iv: iv,
- algorithm: algorithm,
- keyIsPassphrase: usePassphrase
- };
- if (algorithm === 'gcm') {
- let authTag = input.authTag || config.authTag;
- if (authTag && typeof authTag === 'string') {
- authTag = interpolate(authTag, input);
- }
- if (!authTag) {
- throw new Error('Authentication tag is required for AES-GCM decryption');
- }
- decryptOptions.authTag = authTag;
- }
- smartbotic.log.info('Decrypting with AES-256-' + algorithm.toUpperCase());
- const result = smartbotic.crypto.aesDecrypt(decryptOptions);
- if (result.error) {
- throw new Error('Decryption failed: ' + result.error);
- }
- let outputResult = result.plaintext;
- if (outputDataFormat === 'base64') {
- outputResult = smartbotic.utils.base64Encode(result.plaintext);
- }
- return {
- result: outputResult,
- operation: 'decrypt',
- algorithm: encryptionAlgorithm
- };
- }
- function executeRsaDecrypt(config, input, ciphertext, outputDataFormat) {
- let privateKey = input.privateKey || input.decryptionKey || config.decryptionKey;
- if (privateKey && typeof privateKey === 'string') {
- privateKey = interpolate(privateKey, input);
- }
- if (!privateKey) {
- throw new Error('Private key is required for RSA decryption');
- }
- if (!privateKey.includes('-----BEGIN')) {
- throw new Error('Private key must be in PEM format (starting with -----BEGIN PRIVATE KEY----- or -----BEGIN RSA PRIVATE KEY-----)');
- }
- smartbotic.log.info('Decrypting with RSA');
- const result = smartbotic.crypto.rsaDecrypt({
- ciphertext: ciphertext,
- privateKey: privateKey
- });
- if (result.error) {
- throw new Error('RSA decryption failed: ' + result.error);
- }
- let outputResult = result.plaintext;
- if (outputDataFormat === 'base64') {
- outputResult = smartbotic.utils.base64Encode(result.plaintext);
- }
- return {
- result: outputResult,
- operation: 'decrypt',
- algorithm: 'rsa'
- };
- }
- module.exports = { configSchema, inputSchema, outputSchema, execute };
|