mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-20 17:11:17 +00:00
61 lines
1.7 KiB
TypeScript
61 lines
1.7 KiB
TypeScript
|
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto"
|
||
|
|
|
||
|
|
const ALGORITHM = "aes-256-gcm"
|
||
|
|
const IV_LENGTH = 12
|
||
|
|
const AUTH_TAG_LENGTH = 16
|
||
|
|
|
||
|
|
/**
|
||
|
|
* AES-256-GCM encryption for credential storage.
|
||
|
|
*
|
||
|
|
* Caches the parsed key on construction to avoid repeated
|
||
|
|
* env reads and Buffer allocations.
|
||
|
|
*/
|
||
|
|
export class CredentialEncryptor {
|
||
|
|
private readonly key: Buffer
|
||
|
|
|
||
|
|
constructor(base64Key: string) {
|
||
|
|
const key = Buffer.from(base64Key, "base64")
|
||
|
|
if (key.length !== 32) {
|
||
|
|
throw new Error(
|
||
|
|
`Encryption key must be 32 bytes (got ${key.length}). Generate with: openssl rand -base64 32`,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
this.key = key
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Encrypts plaintext using AES-256-GCM.
|
||
|
|
*
|
||
|
|
* Output format: [12-byte IV][ciphertext][16-byte auth tag]
|
||
|
|
*/
|
||
|
|
encrypt(plaintext: string): Buffer {
|
||
|
|
const iv = randomBytes(IV_LENGTH)
|
||
|
|
const cipher = createCipheriv(ALGORITHM, this.key, iv)
|
||
|
|
|
||
|
|
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()])
|
||
|
|
const authTag = cipher.getAuthTag()
|
||
|
|
|
||
|
|
return Buffer.concat([iv, encrypted, authTag])
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Decrypts a buffer produced by `encrypt`.
|
||
|
|
*
|
||
|
|
* Expects format: [12-byte IV][ciphertext][16-byte auth tag]
|
||
|
|
*/
|
||
|
|
decrypt(data: Buffer): string {
|
||
|
|
if (data.length < IV_LENGTH + AUTH_TAG_LENGTH) {
|
||
|
|
throw new Error("Encrypted data is too short")
|
||
|
|
}
|
||
|
|
|
||
|
|
const iv = data.subarray(0, IV_LENGTH)
|
||
|
|
const authTag = data.subarray(data.length - AUTH_TAG_LENGTH)
|
||
|
|
const ciphertext = data.subarray(IV_LENGTH, data.length - AUTH_TAG_LENGTH)
|
||
|
|
|
||
|
|
const decipher = createDecipheriv(ALGORITHM, this.key, iv)
|
||
|
|
decipher.setAuthTag(authTag)
|
||
|
|
|
||
|
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8")
|
||
|
|
}
|
||
|
|
}
|