Created
August 28, 2025 18:09
-
-
Save GoodnessEzeokafor/cf50aad495b21c4b44e5d56d8feb2a86 to your computer and use it in GitHub Desktop.
encryption.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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