DEV Community

LS
LS

Posted on

How to use crypto library in Nodejs

#!/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();

Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Top comments (0)