Files
hub/src/crypto/mod.ts
glm-5.1 2b63cda1c7 Setup repo: migrate architecture specs, code stubs, and tasks from alkhub_ts
Copy architecture docs, ADRs, storage domain specs, research, reviews,
and 56 storage architecture tasks from the alkhub_ts monorepo. Adapt for
standalone @alkdev/hub repo structure (src/ not packages/hub/).

Sanitize all sensitive information:
- Replace private IPs (10.0.0.1) with localhost defaults
- Remove internal server hostnames (dev1, ns528096)
- Replace /workspace/ private paths with npm package references
- Remove hardcoded credentials from examples
- Rewrite infrastructure.md without private network details

Add Deno project scaffolding: deno.json (pinned deps), .gitignore,
AGENTS.md, entry point. Migrate existing code stubs (crypto, config
types, logger) with updated import paths.
2026-05-25 10:56:32 +00:00

119 lines
2.7 KiB
TypeScript

import { encodeBase64, decodeBase64 } from "@std/encoding";
/**
* Encrypted data structure with key version support
*/
export interface EncryptedData {
keyVersion: number;
salt: string;
iv: string;
data: string;
}
/**
* Generate a random salt (16 bytes recommended for PBKDF2)
*/
function generateSalt(): Uint8Array {
return crypto.getRandomValues(new Uint8Array(16));
}
/**
* Generate a random IV (12 bytes recommended for AES-GCM)
*/
function generateIV(): Uint8Array {
return crypto.getRandomValues(new Uint8Array(12));
}
/**
* Derive a key from a password using PBKDF2
*/
async function deriveKey(
password: string,
salt: Uint8Array,
keyVersion: number = 1
): Promise<CryptoKey> {
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
"raw",
encoder.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"]
);
// Adjust iterations based on key version (for future rotation)
const iterations = keyVersion === 1 ? 100000 : 200000;
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: new Uint8Array(salt),
iterations,
hash: "SHA-256"
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"]
);
}
/**
* Encrypt data using AES-GCM with key version support
*/
export async function encrypt(
plaintext: string,
password:string,
keyVersion: number = 1
): Promise<EncryptedData> {
const encoder = new TextEncoder();
const salt = generateSalt();
const iv = generateIV();
const key = await deriveKey(password, salt, keyVersion);
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: new Uint8Array(iv) },
key,
encoder.encode(plaintext)
);
return {
keyVersion,
salt: encodeBase64(salt),
iv: encodeBase64(iv),
data: encodeBase64(new Uint8Array(encrypted))
};
}
/**
* Decrypt data using AES-GCM with key version support
*/
export async function decrypt(encryptedData: EncryptedData, password:string): Promise<string> {
const decoder = new TextDecoder();
const salt = decodeBase64(encryptedData.salt);
const iv = decodeBase64(encryptedData.iv);
const data = decodeBase64(encryptedData.data);
const key = await deriveKey(password, salt, encryptedData.keyVersion);
try {
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: new Uint8Array(iv) },
key,
data
);
return decoder.decode(decrypted);
} catch (_error) {
throw new Error("Decryption failed: Invalid data or key");
}
}
/**
* Generate a secure random encryption key
*/
export function generateEncryptionKey(): string {
const bytes = crypto.getRandomValues(new Uint8Array(32));
return encodeBase64(bytes);
}