| import crypto from 'crypto'; |
| import fs from 'fs/promises'; |
| import path from 'path'; |
|
|
| const ALGORITHM = 'aes-256-gcm'; |
| const KEY_LENGTH = 32; |
| const IV_LENGTH = 16; |
| const AUTH_TAG_LENGTH = 16; |
|
|
| |
| function getKey() { |
| const keyEnv = process.env.DATA_ENCRYPTION_KEY; |
| if (!keyEnv) throw new Error('DATA_ENCRYPTION_KEY environment variable not set'); |
| |
| return crypto.createHash('sha256').update(keyEnv).digest(); |
| } |
|
|
| export function encryptJson(data) { |
| const key = getKey(); |
| const iv = crypto.randomBytes(IV_LENGTH); |
| const cipher = crypto.createCipher(ALGORITHM, key); |
| cipher.setAAD(Buffer.from('')); |
|
|
| let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex'); |
| encrypted += cipher.final('hex'); |
|
|
| const authTag = cipher.getAuthTag(); |
| return { |
| iv: iv.toString('hex'), |
| encrypted, |
| authTag: authTag.toString('hex'), |
| }; |
| } |
|
|
| export function decryptJson(encryptedData) { |
| const key = getKey(); |
| const { iv, encrypted, authTag } = encryptedData; |
| const decipher = crypto.createDecipher(ALGORITHM, key); |
| decipher.setAuthTag(Buffer.from(authTag, 'hex')); |
| decipher.setAAD(Buffer.from('')); |
|
|
| let decrypted = decipher.update(encrypted, 'hex', 'utf8'); |
| decrypted += decipher.final('utf8'); |
| return JSON.parse(decrypted); |
| } |
|
|
| export async function saveEncryptedJson(filePath, data) { |
| const encrypted = encryptJson(data); |
| await fs.mkdir(path.dirname(filePath), { recursive: true }); |
| await fs.writeFile(filePath, JSON.stringify(encrypted)); |
| } |
|
|
| export async function loadEncryptedJson(filePath) { |
| try { |
| const content = await fs.readFile(filePath, 'utf8'); |
| const encrypted = JSON.parse(content); |
| return decryptJson(encrypted); |
| } catch (err) { |
| if (err.code === 'ENOENT') return null; |
| throw err; |
| } |
| } |