UNPKG

@river-build/sdk

Version:

For more details, visit the following resources:

99 lines 4.52 kB
// This function is a helper for encrypting and decrypting public content. // The same IV and key are generated from the key phrase each time. // Not intended for protecting sensitive data, but rather for obfuscating content. import { throwWithCode } from '@river-build/dlog'; import { Err } from '@river-build/proto'; import { AES_GCM_DERIVED_ALGORITHM } from '@river-build/encryption'; import crypto from 'crypto'; export function uint8ArrayToBase64(uint8Array) { return Buffer.from(uint8Array).toString('base64'); } export function base64ToUint8Array(base64) { const buffer = Buffer.from(base64, 'base64'); return new Uint8Array(buffer); } function bufferToUint8Array(buffer) { return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); } function uint8ArrayToBuffer(uint8Array) { return Buffer.from(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength); } async function getExtendedKeyMaterial(seedBuffer, length) { const hash = crypto.createHash('sha256'); hash.update(uint8ArrayToBuffer(seedBuffer)); let keyMaterial = bufferToUint8Array(hash.digest()); while (keyMaterial.length < length) { const newHash = crypto.createHash('sha256'); newHash.update(uint8ArrayToBuffer(keyMaterial)); keyMaterial = new Uint8Array([...keyMaterial, ...bufferToUint8Array(newHash.digest())]); } return keyMaterial.slice(0, length); } export async function deriveKeyAndIV(keyPhrase) { let keyBuffer; if (typeof keyPhrase === 'string') { const encoder = new TextEncoder(); keyBuffer = encoder.encode(keyPhrase); } else { keyBuffer = keyPhrase; } const keyMaterial = await getExtendedKeyMaterial(keyBuffer, 32 + 12); // 32 bytes for key, 12 bytes for IV const key = keyMaterial.slice(0, 32); // AES-256 key const iv = keyMaterial.slice(32, 32 + 12); // AES-GCM IV return { key, iv }; } export async function encryptAESGCM(data, key, iv) { if (!data || data.length === 0) { throw new Error('Cannot encrypt undefined or empty data'); } if (!key) { key = crypto.randomBytes(32); } else if (key.length !== 32) { throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.'); } if (!iv) { iv = crypto.randomBytes(12); } else if (iv.length !== 12) { throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.'); } const cipher = crypto.createCipheriv('aes-256-gcm', uint8ArrayToBuffer(key), uint8ArrayToBuffer(iv)); const encrypted = Buffer.concat([cipher.update(uint8ArrayToBuffer(data)), cipher.final()]); const authTag = cipher.getAuthTag(); const ciphertext = Buffer.concat([encrypted, authTag]); return { ciphertext: bufferToUint8Array(ciphertext), iv, secretKey: key }; } export async function decryptAESGCM(data, key, iv) { if (key.length !== 32) { throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.'); } if (iv.length !== 12) { throw new Error('Invalid IV length. AES-256-GCM requires a 12-byte IV.'); } // Convert data to Uint8Array if it is a string let dataBuffer; if (typeof data === 'string') { dataBuffer = Buffer.from(data, 'base64'); } else { dataBuffer = data; } const encryptedBuffer = Buffer.from(dataBuffer.buffer, dataBuffer.byteOffset, dataBuffer.byteLength); const authTag = new Uint8Array(encryptedBuffer.buffer.slice(encryptedBuffer.byteOffset + encryptedBuffer.length - 16, encryptedBuffer.byteOffset + encryptedBuffer.length)); const encryptedContent = new Uint8Array(encryptedBuffer.buffer.slice(encryptedBuffer.byteOffset, encryptedBuffer.byteOffset + encryptedBuffer.length - 16)); const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(authTag); const decrypted = Buffer.concat([decipher.update(encryptedContent), decipher.final()]); return new Uint8Array(decrypted.buffer, decrypted.byteOffset, decrypted.byteLength); } export async function decryptDerivedAESGCM(keyPhrase, encryptedData) { if (encryptedData.algorithm !== AES_GCM_DERIVED_ALGORITHM) { throwWithCode(`${encryptedData.algorithm}" algorithm not implemented`, Err.UNIMPLEMENTED); } const { key, iv } = await deriveKeyAndIV(keyPhrase); const ciphertext = base64ToUint8Array(encryptedData.ciphertext); return decryptAESGCM(ciphertext, key, iv); } //# sourceMappingURL=crypto_utils.js.map