/** * Reimplementation of string-crypto v2 encryption * Uses Node's built-in crypto module (works in Bun too) */ import { randomBytes, pbkdf2Sync, createCipheriv, createDecipheriv, } from "crypto"; const KEYLEN = 256 / 8; // 32 bytes for aes-256 interface DeriveKeyOpts { salt?: string; iterations?: number; digest?: string; } const defaultOpts: DeriveKeyOpts = { salt: "s41t", iterations: 1, digest: "sha512", }; export function deriveKey(password: string, options?: DeriveKeyOpts): Buffer { const { salt, iterations, digest } = { ...defaultOpts, ...options }; return pbkdf2Sync(password, salt!, iterations!, KEYLEN, digest!); } export function encryptString(str: string, password: string): string { const derivedKey = deriveKey(password); const iv = randomBytes(16); const cipher = createCipheriv("aes-256-gcm", derivedKey, iv); let encryptedBase64 = cipher.update(str, "utf8", "base64"); encryptedBase64 += cipher.final("base64"); // Convert base64 to hex (this is the v2 quirk) const encryptedHex = Buffer.from(encryptedBase64).toString("hex"); const ivHex = iv.toString("hex"); return `${ivHex}:${encryptedHex}`; } export function decryptString(encryptedStr: string, password: string): string { const derivedKey = deriveKey(password); const [ivHex, encryptedHex] = encryptedStr.split(":"); const iv = Buffer.from(ivHex, "hex"); const encryptedBase64 = Buffer.from(encryptedHex, "hex").toString(); const decipher = createDecipheriv("aes-256-gcm", derivedKey, iv); let decrypted = decipher.update(encryptedBase64, "base64", "utf8"); return decrypted; } // Test if (import.meta.main) { // Load .env const envFile = Bun.file(".env"); if (await envFile.exists()) { const envContent = await envFile.text(); for (const line of envContent.split("\n")) { const [key, ...valueParts] = line.split("="); if (key && valueParts.length) { process.env[key.trim()] = valueParts.join("=").trim(); } } } const email = process.env.ANYCLIP_EMAIL || process.env.ANYCLIP_USER; const plaintext = process.env.ANYCLIP_PASSWORD; if (!email || !plaintext) { console.error("Set ANYCLIP_EMAIL and ANYCLIP_PASSWORD in .env"); process.exit(1); } const salt = "$2b$04$wwky7rvtr6BFNaCqntwyie"; const encrypted = encryptString(plaintext, salt); console.log("Encrypted:", encrypted); // Test with API console.log("\nTesting with AnyClip API..."); const response = await fetch( "https://videomanager-api.anyclip.com/public/auth/login", { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ email, password: encrypted, }), } ); console.log("Status:", response.status); if (response.ok) { console.log("✅ Works without string-crypto library!"); } else { const text = await response.text(); console.log("Response:", text); } }