40 lines
1.6 KiB
JavaScript
40 lines
1.6 KiB
JavaScript
// services/cryptoUtil.js
|
|
// Symmetric encryption for secrets we must be able to READ back (unlike
|
|
// portal passwords, which are one-way hashed) — specifically each user's SAP
|
|
// Service-Layer password, which has to be replayed to SAP at login time.
|
|
// AES-256-GCM with a key derived from JWT_SECRET. Output is a self-describing
|
|
// string "v1:<iv>:<tag>:<ciphertext>" (all base64), so decrypt needs no extra
|
|
// state. NOT for passwords you only ever compare — use hashing for those.
|
|
'use strict';
|
|
const crypto = require('crypto');
|
|
|
|
function key() {
|
|
const secret = process.env.JWT_SECRET || 'sap-portal-secret';
|
|
return crypto.createHash('sha256').update('sapcred:' + secret).digest(); // 32 bytes
|
|
}
|
|
|
|
function encrypt(plain) {
|
|
if (plain == null || plain === '') return '';
|
|
const iv = crypto.randomBytes(12);
|
|
const cipher = crypto.createCipheriv('aes-256-gcm', key(), iv);
|
|
const enc = Buffer.concat([cipher.update(String(plain), 'utf8'), cipher.final()]);
|
|
const tag = cipher.getAuthTag();
|
|
return `v1:${iv.toString('base64')}:${tag.toString('base64')}:${enc.toString('base64')}`;
|
|
}
|
|
|
|
function decrypt(blob) {
|
|
if (!blob) return '';
|
|
try {
|
|
const parts = String(blob).split(':');
|
|
if (parts.length !== 4 || parts[0] !== 'v1') return '';
|
|
const iv = Buffer.from(parts[1], 'base64');
|
|
const tag = Buffer.from(parts[2], 'base64');
|
|
const data = Buffer.from(parts[3], 'base64');
|
|
const decipher = crypto.createDecipheriv('aes-256-gcm', key(), iv);
|
|
decipher.setAuthTag(tag);
|
|
return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8');
|
|
} catch { return ''; }
|
|
}
|
|
|
|
module.exports = { encrypt, decrypt };
|