Skip to content

Instantly share code, notes, and snippets.

@skorotkiewicz
Created December 15, 2024 17:50
Show Gist options
  • Save skorotkiewicz/53ed107d45db2cc4ff76f135b93b28e9 to your computer and use it in GitHub Desktop.
Save skorotkiewicz/53ed107d45db2cc4ff76f135b93b28e9 to your computer and use it in GitHub Desktop.
Crypto messages for postcards
import crypto from "node:crypto";
/**
* Generates a compact key from a seed password
* @param {string} seed - The seed password used to generate the key
* @param {number} [length=16] - The desired length of the generated key
* @returns {string} A compact key derived from the seed
*/
function generateCompactKey(seed, length = 16) {
// Use SHA-256 to generate a deterministic but unique key
const hash = crypto.createHash("sha256").update(seed).digest("hex");
return hash.slice(0, length);
}
/**
* Encrypts a message using a seed-derived key
* @param {string} seed - The seed password used to generate the encryption key
* @param {string} message - The message to be encrypted
* @returns {string} The encrypted message in a URL-safe base64 format
*/
function encode(seed, message) {
// Generate a compact key
const key = generateCompactKey(seed);
// Create a cipher
const cipher = crypto.createCipheriv("aes-128-ecb", key, Buffer.alloc(0));
// Encrypt the message
let encrypted = cipher.update(message, "utf8", "base64");
encrypted += cipher.final("base64");
// Remove unsafe characters from base64
return encrypted
.replace(/\+/g, "-") // replace + with -
.replace(/\//g, "_") // replace / with _
.replace(/=/g, ""); // remove padding
}
/**
* Decrypts a message using a seed-derived key
* @param {string} seed - The seed password used to generate the decryption key
* @param {string} encodedMessage - The encrypted message to be decrypted
* @returns {string} The decrypted message
*/
function decode(seed, encodedMessage) {
// Restore padding
const paddedMessage =
encodedMessage
.replace(/-/g, "+") // replace - with +
.replace(/_/g, "/") + // replace _ with /
"===".slice(0, (4 - (encodedMessage.length % 4)) % 4);
// Generate the same compact key
const key = generateCompactKey(seed);
// Create a decipher
const decipher = crypto.createDecipheriv("aes-128-ecb", key, Buffer.alloc(0));
// Decrypt the message
let decrypted = decipher.update(paddedMessage, "base64", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
// Usage example
const seed = "postcard2024";
const message = "meet me at 4pm";
try {
const encoded = encode(seed, message);
console.log("Encoded message:", encoded);
const decoded = decode(seed, encoded);
console.log("Decoded message:", decoded);
} catch (error) {
console.error("Encryption/decryption error:", error);
}
// Encoded message: bgGmGReREFMscWwms4sboQ
// Decoded message: meet me at 4pm
import crypto from "node:crypto";
/**
* Generates a key and Initialization Vector (IV) from a seed password
* @param {string} seed - The seed password used to generate the key and IV
* @param {number} [keyLength=32] - The desired length of the encryption key
* @param {number} [ivLength=16] - The desired length of the Initialization Vector
* @returns {Object} An object containing the generated key, IV, and salt
*/
function generateKeyAndIV(seed, keyLength = 32, ivLength = 16) {
const salt = crypto.randomBytes(16);
const key = crypto.scryptSync(seed, salt, keyLength);
const iv = crypto.randomBytes(ivLength);
return { key, iv, salt };
}
/**
* Encrypts a message using a seed-derived key and IV
* @param {string} seed - The seed password used to generate the encryption key
* @param {string} message - The message to be encrypted
* @returns {string} The encrypted message with salt and IV in a hex-encoded format
*/
function encode(seed, message) {
// Generate key and IV
const { key, iv, salt } = generateKeyAndIV(seed);
// Create a cipher
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
// Encrypt the message
let encrypted = cipher.update(message, "utf8", "hex");
encrypted += cipher.final("hex");
// Return salt, IV, and encrypted message
return `${salt.toString("hex")}:${iv.toString("hex")}:${encrypted}`;
}
/**
* Decrypts a message using a seed-derived key
* @param {string} seed - The seed password used to generate the decryption key
* @param {string} encodedMessage - The encrypted message to be decrypted
* @returns {string} The decrypted message
*/
function decode(seed, encodedMessage) {
// Split the encoded message into components
const [saltHex, ivHex, encryptedHex] = encodedMessage.split(":");
// Convert salt and IV from hex
const salt = Buffer.from(saltHex, "hex");
const iv = Buffer.from(ivHex, "hex");
// Generate key using the same salt
const key = crypto.scryptSync(seed, salt, 32);
// Create a decipher
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
// Decrypt the message
let decrypted = decipher.update(encryptedHex, "hex", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
// Usage example
const seed = "postcard2024";
const message = "meet me at 4pm";
try {
const encoded = encode(seed, message);
console.log("Encoded message:", encoded);
const decoded = decode(seed, encoded);
console.log("Decoded message:", decoded);
} catch (error) {
console.error("Encryption/decryption error:", error);
}
// Encoded message: 144f62fcf27ae1868ca855cfc55cdc17:50b07bf6f7166177ed5da0605065cc42:db98629710e02e31df718835c3a2313b
// Decoded message: meet me at 4pm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment