aiostreams / packages /core /src /utils /crypto.ts
f4b404's picture
Upload folder using huggingface_hub
f55c62a verified
Raw
History Blame Contribute Delete
5.12 kB
import { config as appConfig } from '../config/index.js';
import {
randomBytes,
createCipheriv,
createDecipheriv,
createHash,
pbkdf2Sync,
randomUUID,
} from 'crypto';
import { genSalt, hash, compare } from 'bcrypt';
import { deflateSync, inflateSync } from 'zlib';
import { createLogger } from '../logging/logger.js';
import { fromUrlSafeBase64, toUrlSafeBase64 } from './general.js';
const logger = createLogger('crypto');
const saltRounds = 10;
export const compressData = (data: string): Buffer => {
return deflateSync(Buffer.from(data, 'utf-8'), {
level: 9,
});
};
export const decompressData = (data: Buffer): string => {
return inflateSync(data).toString('utf-8');
};
const encryptData = (
secretKey: Buffer,
data: Buffer
): { i: string; d: string } => {
// Then encrypt the compressed data
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-cbc', secretKey, iv);
const encryptedData = Buffer.concat([cipher.update(data), cipher.final()]);
return {
i: iv.toString('base64'),
d: encryptedData.toString('base64'),
};
};
const decryptData = (
secretKey: Buffer,
encryptedData: Buffer,
iv: Buffer
): Buffer => {
const decipher = createDecipheriv('aes-256-cbc', secretKey, iv);
// Decrypt the data
const decryptedData = Buffer.concat([
decipher.update(encryptedData),
decipher.final(),
]);
return decryptedData;
};
type SuccessResponse = {
success: true;
data: string;
error: null;
};
type ErrorResponse = {
success: false;
error: string;
data: null;
};
export type Response = SuccessResponse | ErrorResponse;
export function isEncrypted(data: string): boolean {
try {
// parse the data as json
const json = JSON.parse(fromUrlSafeBase64(data));
return ['aioEncrypt', 'a'].includes(json.type || json.t);
} catch (error) {
return false;
}
}
/**
* Encrypts a string using AES-256-CBC encryption, returns a string in the format "iv:encrypted" where
* iv and encrypted are url encoded.
* @param data Data to encrypt
* @param secretKey Secret key used for encryption
* @returns Encrypted data or error message
*/
export function encryptString(data: string, secretKey?: Buffer): Response {
if (!secretKey) {
secretKey = Buffer.from(appConfig.bootstrap.secretKey, 'hex');
}
try {
const compressed = compressData(data);
const { i, d: e } = encryptData(secretKey, compressed);
return {
success: true,
data: toUrlSafeBase64(JSON.stringify({ i, e, t: 'a' })),
error: null,
};
} catch (error: any) {
logger.error(`Failed to encrypt data: ${error.message}`);
return {
success: false,
error: error.message,
data: null,
};
}
}
/**
* Decrypts a string using AES-256-CBC encryption
* @param data Encrypted data to decrypt
* @param secretKey Secret key used for encryption
* @returns Decrypted data or error message
*/
export function decryptString(data: string, secretKey?: Buffer): Response {
if (!secretKey) {
secretKey = Buffer.from(appConfig.bootstrap.secretKey, 'hex');
}
try {
if (!isEncrypted(data)) {
throw new Error('The data was not in an expected encrypted format');
}
const json = JSON.parse(fromUrlSafeBase64(data));
const iv = Buffer.from(json.iv || json.i, 'base64');
const encrypted = Buffer.from(json.encrypted || json.e, 'base64');
const decrypted = decryptData(secretKey, encrypted, iv);
const decompressed = decompressData(decrypted);
return {
success: true,
data: decompressed,
error: null,
};
} catch (error: any) {
logger.error(`Failed to decrypt data: ${error.message}`);
return {
success: false,
error: error.message,
data: null,
};
}
}
export function getSimpleTextHash(text: string): string {
return createHash('sha256').update(text).digest('hex');
}
/**
* Creates a secure hash of text using PBKDF2
* @param text Text to hash
* @returns Object containing the hash and salt used
*/
export async function getTextHash(text: string): Promise<string> {
return await hash(text, await genSalt(saltRounds));
}
/**
* Verifies if the provided text matches a previously generated hash
* @param text Text to verify
* @param storedHash Previously generated hash
* @returns Boolean indicating if the text matches the hash
*/
export async function verifyHash(
text: string,
storedHash: string
): Promise<boolean> {
return compare(text, storedHash);
}
/**
* Derives a 64 character hex string from a password using PBKDF2
* @param password Password to derive key from
* @param salt Optional salt, will be generated if not provided
* @returns Object containing the key and salt used
*/
export async function deriveKey(
password: string,
salt?: string
): Promise<{ key: Buffer; salt: string }> {
salt = salt || (await genSalt(saltRounds));
const key = pbkdf2Sync(
Buffer.from(password, 'utf-8'),
Buffer.from(salt, 'hex'),
100000,
32,
'sha512'
);
return { key, salt };
}
export function generateUUID(): string {
return randomUUID();
}