cps_b2c / crypto_utils_b2c.js
Rakshitjan's picture
Create crypto_utils_b2c.js
3b5eda5 verified
const crypto = require("crypto");
/**
* crypto_utils.js
* Hybrid Encryption: AES-256-GCM + RSA-OAEP + SHA256withRSA
*
* Functions Exposed:
* encryptString(text)
* decryptString(encryptedText)
*
* Required ENV variables:
* PRIVATE_KEY (PKCS8 private key)
* PUBLIC_CERT (X.509 certificate)
*/
/* -------------------------------------------------------
PEM NORMALIZATION HELPERS (for raw Base64 input)
-------------------------------------------------------- */
function wrapPrivateKey(key) {
if (!key) throw new Error("Missing PRIVATE_KEY");
if (key.includes("BEGIN")) return key; // Already PEM
const clean = key.replace(/\s+/g, "");
return `-----BEGIN PRIVATE KEY-----
${clean.match(/.{1,64}/g).join("\n")}
-----END PRIVATE KEY-----`;
}
function wrapCertificate(cert) {
if (!cert) throw new Error("Missing PUBLIC_CERT");
if (cert.includes("BEGIN")) return cert; // Already PEM
const clean = cert.replace(/\s+/g, "");
return `-----BEGIN CERTIFICATE-----
${clean.match(/.{1,64}/g).join("\n")}
-----END CERTIFICATE-----`;
}
/* -------------------------------------------------------
LOAD KEYS
-------------------------------------------------------- */
const PRIVATE_KEY = wrapPrivateKey(process.env.PRIVATE_KEY);
const PUBLIC_CERT = wrapCertificate(process.env.PUBLIC_CERT);
/* -------------------------------------------------------
AES-256-GCM IMPLEMENTATION
-------------------------------------------------------- */
function aesEncrypt(plainText, key, iv) {
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
const encrypted = Buffer.concat([
cipher.update(plainText, "utf8"),
cipher.final()
]);
const tag = cipher.getAuthTag();
return Buffer.concat([encrypted, tag]).toString("base64");
}
function aesDecrypt(cipherBase64, key, iv) {
const buffer = Buffer.from(cipherBase64, "base64");
const tag = buffer.slice(buffer.length - 16);
const ciphertext = buffer.slice(0, buffer.length - 16);
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([
decipher.update(ciphertext),
decipher.final()
]);
return decrypted.toString("utf8");
}
/* -------------------------------------------------------
RSA HELPERS
-------------------------------------------------------- */
function rsaEncryptBase64(str) {
return crypto
.publicEncrypt(
{
key: PUBLIC_CERT,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
},
Buffer.from(str, "utf8")
)
.toString("base64");
}
function rsaDecryptToString(b64) {
return crypto
.privateDecrypt(
{
key: PRIVATE_KEY,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
},
Buffer.from(b64, "base64")
)
.toString("utf8");
}
/* -------------------------------------------------------
SIGN / VERIFY HELPERS
-------------------------------------------------------- */
function signData(data) {
const sign = crypto.createSign("RSA-SHA256");
sign.update(data);
sign.end();
return sign.sign(PRIVATE_KEY).toString("base64");
}
function verifyData(data, signatureB64) {
const verify = crypto.createVerify("RSA-SHA256");
verify.update(data);
verify.end();
return verify.verify(PUBLIC_CERT, Buffer.from(signatureB64, "base64"));
}
/* -------------------------------------------------------
PUBLIC FUNCTIONS
-------------------------------------------------------- */
/**
* Encrypt a plain string using:
* AES-256-GCM + RSA-OAEP + SHA256withRSA
*/
function encryptStringB2C(text) {
try {
const aesKey = crypto.randomBytes(32); // 256-bit
const iv = crypto.randomBytes(16); // 128-bit
// AES encrypt
const aesCipher = aesEncrypt(text, aesKey, iv);
// Signature over AES ciphertext (Base64)
const signature = signData(aesCipher);
// RSA encrypted AES key
const encryptedKey = rsaEncryptBase64(aesKey.toString("base64"));
// Assemble: header : iv : cipher : signature
const payload = [
encryptedKey,
iv.toString("base64"),
aesCipher,
signature
].join(":");
// Final base64 wrapper
return Buffer.from(payload, "utf8").toString("base64");
} catch (err) {
console.error("Encryption error:", err.message);
throw new Error("Failed to encrypt data");
}
}
/**
* Decrypt the hybrid AES/RSA encrypted Base64 payload
*/
function decryptStringB2C(encryptedText) {
try {
const decoded = Buffer.from(encryptedText, "base64").toString("utf8");
const parts = decoded.split(":");
if (parts.length < 4) throw new Error("Invalid encrypted payload");
const [encryptedKey, iv64, cipher64, signature64] = parts;
// RSA decrypt AES key
const aesKeyBase64 = rsaDecryptToString(encryptedKey);
const aesKey = Buffer.from(aesKeyBase64, "base64");
// Verify signature
if (!verifyData(cipher64, signature64)) {
throw new Error("Signature verification failed");
}
const iv = Buffer.from(iv64, "base64");
// AES decrypt
return aesDecrypt(cipher64, aesKey, iv);
} catch (err) {
console.error("Decryption error:", err.message);
throw new Error("Failed to decrypt data");
}
}
/* -------------------------------------------------------
EXPORTS
-------------------------------------------------------- */
module.exports = {
encryptStringB2C,
decryptStringB2C
};