Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save GoodnessEzeokafor/cf50aad495b21c4b44e5d56d8feb2a86 to your computer and use it in GitHub Desktop.
Save GoodnessEzeokafor/cf50aad495b21c4b44e5d56d8feb2a86 to your computer and use it in GitHub Desktop.
encryption.js
// utils/sendCrypto.js
import crypto from "crypto";
import dotenv from "dotenv";
import fernetCjs from "fernet"; // CJS package -> default import, then destructure
const { Token, Secret } = fernetCjs;
dotenv.config();
const SEND_ENCRYPTION_KEY = process.env.SEND_ENCRYPTION_KEY;
// Base64url with padding (Python's urlsafe_b64encode keeps padding)
function base64UrlWithPadding(buf) {
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_");
}
export function generateSecretKey(password, salt = null) {
const saltBuf = salt ? Buffer.from(salt) : crypto.randomBytes(16);
const derived = crypto.pbkdf2Sync(password, saltBuf, 100000, 32, "sha256");
const keyB64Url = base64UrlWithPadding(derived);
return { keyB64Url, salt: saltBuf };
}
export function sendEncryptPayload(payload, secretKey = null) {
if (!secretKey) {
if (!SEND_ENCRYPTION_KEY) throw new Error("SEND_ENCRYPTION_KEY is not set.");
secretKey = SEND_ENCRYPTION_KEY;
}
// Derive Fernet key + salt
const { keyB64Url, salt } = generateSecretKey(secretKey);
// Add timestamp and stringify
const payloadWithTs = { ...payload, timestamp: Math.floor(Date.now() / 1000) };
const jsonPayload = JSON.stringify(payloadWithTs);
// Fernet encode returns base64url ASCII string — keep as ASCII bytes
const secret = new Secret(keyB64Url);
const token = new Token({ secret });
const fernetTokenB64Url = token.encode(jsonPayload); // string
const tokenAsciiBytes = Buffer.from(fernetTokenB64Url, "utf8");
// Python parity: combined = salt(16 raw) + ASCII(fernet_token)
const combined = Buffer.concat([salt, tokenAsciiBytes]);
// Transport: standard base64
return combined.toString("base64");
}
export function sendDecryptPayload(encryptedDataB64, secretKey = null, maxAgeSeconds = 300) {
if (!secretKey) {
if (!SEND_ENCRYPTION_KEY) throw new Error("SEND_ENCRYPTION_KEY is not set.");
secretKey = SEND_ENCRYPTION_KEY;
}
// 1) base64 decode -> [salt(16)] [ASCII fernet token]
const combined = Buffer.from(encryptedDataB64, "base64");
const salt = combined.subarray(0, 16);
const tokenAscii = combined.subarray(16).toString("utf8"); // keep ASCII string
// 2) re-derive key with same salt
const { keyB64Url } = generateSecretKey(secretKey, salt);
// 3) Fernet decrypt
const secret = new Secret(keyB64Url);
const token = new Token({ secret, ttl: 0 }); // ttl handled by our own timestamp check
const plaintext = token.decode(tokenAscii); // throws on invalid token
const data = JSON.parse(plaintext);
// 4) timestamp checks
const ts = data.timestamp;
if (typeof ts !== "number") {
throw new Error("Missing timestamp in payload");
}
const now = Math.floor(Date.now() / 1000);
if (now - ts > maxAgeSeconds) {
throw new Error(`Payload too old. Age: ${now - ts} seconds`);
}
delete data.timestamp;
return data;
}
// const payload = {
// orderId: 12345,
// amount: 5000,
// currency: "NGN",
// };
const encrypted = sendEncryptPayload({
source_currency: "USD",
target_currency: "KES",
amount: 5,
card_number: "5123450000000008",
expiry_month: "01",
expiry_year: "39",
cvv: "100",
payment_method: "bank-transfer",
account_number: "0239573384",
bank_code: "000014",
account_name: "Chibunkem Ojiaku",
}); // uses process.env.SEND_ENCRYPTION_KEY
console.log({
encrypted_payload: encrypted,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment