#!/usr/bin/env node
/**
* secret-encode.js
*
* Usage:
* node secret-encode.js encrypt "my secret text" myPassword
* node secret-encode.js encrypt-file ./input.txt myPassword
* node secret-encode.js decrypt <base64-string> myPassword
* node secret-encode.js decrypt-file <base64-string> myPassword ./out.txt
*
* Output format (JSON string):
* {
* "v": 1, // version
* "salt": "<base64>", // PBKDF2 salt
* "iv": "<base64>", // AES-GCM IV
* "tag": "<base64>", // AES-GCM auth tag
* "ct": "<base64>" // ciphertext
* }
*
* This is reversible: keep the password secret and share the encoded JSON string safely.
*/
const crypto = require('crypto');
const fs = require('fs').promises;
const path = require('path');
const PBKDF2_ITER = 120_000; // iterations (adjust upward if you want more CPU cost)
const KEYLEN = 32; // 256-bit key
const SALT_LEN = 16;
const IV_LEN = 12; // recommended for GCM
const AUTH_TAG_LEN = 16;
function deriveKey(password, salt) {
return crypto.pbkdf2Sync(password, salt, PBKDF2_ITER, KEYLEN, 'sha256');
}
function encryptBuffer(plainBuf, password) {
const salt = crypto.randomBytes(SALT_LEN);
const key = deriveKey(password, salt);
const iv = crypto.randomBytes(IV_LEN);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: AUTH_TAG_LEN });
const ct = Buffer.concat([cipher.update(plainBuf), cipher.final()]);
const tag = cipher.getAuthTag();
const out = {
v: 1,
salt: salt.toString('base64'),
iv: iv.toString('base64'),
tag: tag.toString('base64'),
ct: ct.toString('base64'),
};
return JSON.stringify(out);
}
function decryptString(encryptedJsonStr, password) {
let obj;
try {
obj = JSON.parse(encryptedJsonStr);
} catch (e) {
throw new Error('Input is not valid JSON. Make sure you pass the encoded JSON string.');
}
if (obj.v !== 1) throw new Error('Unsupported version');
const salt = Buffer.from(obj.salt, 'base64');
const iv = Buffer.from(obj.iv, 'base64');
const tag = Buffer.from(obj.tag, 'base64');
const ct = Buffer.from(obj.ct, 'base64');
const key = deriveKey(password, salt);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv, { authTagLength: AUTH_TAG_LEN });
decipher.setAuthTag(tag);
const plain = Buffer.concat([decipher.update(ct), decipher.final()]);
return plain;
}
async function run() {
const args = process.argv.slice(2);
if (args.length < 1) return printHelp();
const cmd = args[0];
try {
if (cmd === 'encrypt') {
if (args.length < 3) return printHelp();
const text = args[1];
const password = args[2];
const out = encryptBuffer(Buffer.from(text, 'utf8'), password);
console.log(out);
return;
}
if (cmd === 'encrypt-file') {
if (args.length < 3) return printHelp();
const file = args[1];
const password = args[2];
const b = await fs.readFile(file);
const out = encryptBuffer(b, password);
console.log(out);
return;
}
if (cmd === 'decrypt') {
if (args.length < 3) return printHelp();
const encoded = args[1];
const password = args[2];
const plainBuf = decryptString(encoded, password);
console.log(plainBuf.toString('utf8'));
return;
}
if (cmd === 'decrypt-file') {
if (args.length < 4) return printHelp();
const encoded = args[1];
const password = args[2];
const outFile = args[3];
const plainBuf = decryptString(encoded, password);
await fs.writeFile(outFile, plainBuf);
console.log(`Wrote decrypted content to ${outFile}`);
return;
}
printHelp();
} catch (err) {
console.error('Error:', err.message || err);
process.exitCode = 2;
}
}
function printHelp() {
const me = path.basename(process.argv[1]);
console.log(`
Usage:
node ${me} encrypt "my secret text" myPassword
node ${me} encrypt-file ./input.txt myPassword
node ${me} decrypt '<encoded-json-string>' myPassword
node ${me} decrypt-file '<encoded-json-string>' myPassword ./out.txt
Notes:
- The output is a JSON string (salt, iv, tag, ciphertext in base64). Share that string publicly if you like.
- NEVER share the password publicly. Send the password via a separate secure channel (phone, Signal, etc).
- For short secrets you can use "encrypt", for files use "encrypt-file".
`);
}
if (require.main === module) run();
and how to decode
node decrypt-encrypt.js decrypt '{"v":1,"salt":"YkgTYa8bQeuudUVyc6JzTg==","iv":"gYGLAUZFttrwNOHB","tag":"OhQSFgCQuR5iKMQnIh7Jgg==","ct":"43rxWQfb40QNZEEw01EXvDwAH/aRPz7ln6ZgBqah1vIYZGAxkKW4B/xzH/h3u7DEsGMg3kGwZZXTbNp3A/FEiyQhH9gWstmAwy7klJ+hIttCSBVR40WJR6zIKNjVbZDYeDSxsO0HqNGaY7E2gC/ampLwoefl3hHDAeyddsaSk9/4lwji0SYMjrlvFOcHZtMl9CDQiUzkpY7F8zH3TEsu2e8nZuLJojrz3+UEXTdMguGP3hU1YZEAiNfkdW27UiTfgDkHljxm7ZxLKgoe1DBvmnWKIFShixZ2ps1wBYdp6vOyrVv1mXPX3svYSXK5P+f/7WwiuH5QaaVrj1bGSgRmTuT9tfECmToABo9PCi9QoB0vKoRSA/zztTTcG9jSOrkk5GgCmS84r3lBNcZcNyJZ/Iy1IVv8dGJt0r9QjUjPdpGDf1NUTpbrEkUHXdnPpTw8EQNj1mrI88OldqX/xwjayV/2C0rLlS0tRKQCZ0sXSJPBomKL1uPbLGDW8sM6F0iNuMJHoXL6Hd+DvJnmaexDhnW4uHdlwkfM93B9Wqtlzg3wCB80ze1XsQLJml6uP63kbt6rlNG0il1HtuMDOn58oZYAmlciCZ+a/do5dbs7TuxLuOZjVEvh2D8vVA8DCAKT47r0QIaDcrBY8s15+bWJSTnTSVhIXCRs3L3UjINsbNGnvjQjFLpFLjl6El/gGr+9CNyNkmQ3mVEDtZtU8q+0ub6dPawthWzYwsdU7VFC8D04xsfmUBub7QZmWyWPM5BGSTfHITKCtNRwaQvgTyztDQ0fJY8DsK3+ShtdlxBrZY3i2O6PuKL6rEPBxIyqPx4faMwSmBKrvqAYJBgUTdMq6zQVJqugeqVJ7MCEwPDbFW2yUps4IjAqPQhcOQmQ7euSyW/MN3IRzjuqR6iVcv7axCmROK4NAuCNkDpbHHo3rAiJ8WDg1ByMqcM0yKuHUi3E="}' "your secret key"
Top comments (0)