UNPKG

keyvault-cli

Version:

Secure API key management CLI tool

101 lines (100 loc) 4.02 kB
import * as crypto from 'crypto'; import { ed25519 } from '@noble/curves/ed25519'; export class KeyVaultCrypto { /** * Generate deterministic key pair from passphrase * Same passphrase will always generate same keys */ static generateKeyPair(passphrase) { // Create deterministic seed from passphrase + salt const salt = 'keyvault-seed-salt-v1'; const seed = crypto.pbkdf2Sync(passphrase, salt, 100000, 32, 'sha256'); // Generate Ed25519 key pair from seed const privateKeyBytes = ed25519.utils.randomPrivateKey(); // We'll override this // Use seed as private key (32 bytes) const privateKey = seed; const publicKey = ed25519.getPublicKey(privateKey); // Create address from public key hash const address = this.createAddress(publicKey); return { publicKey: Buffer.from(publicKey).toString('hex'), privateKey: Buffer.from(privateKey).toString('hex'), address }; } /** * Create a unique address from public key */ static createAddress(publicKey) { const hash = crypto.createHash('sha256').update(publicKey).digest(); const address = hash.slice(0, 20).toString('hex'); return '0x' + address; } /** * Encrypt data with public key (for server storage) */ static encrypt(data, publicKeyHex) { try { // For now, use symmetric encryption with derived key from public key // In production, you'd use proper asymmetric encryption const key = crypto.createHash('sha256').update(Buffer.from(publicKeyHex, 'hex')).digest(); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); // Combine IV + encrypted data return iv.toString('hex') + ':' + encrypted; } catch (error) { throw new Error('Encryption failed: ' + error.message); } } /** * Decrypt data with private key (client-side only) */ static decrypt(encryptedData, privateKeyHex) { try { const [ivHex, encrypted] = encryptedData.split(':'); if (!ivHex || !encrypted) { throw new Error('Invalid encrypted data format'); } // Derive same key from private key const publicKey = ed25519.getPublicKey(Buffer.from(privateKeyHex, 'hex')); const key = crypto.createHash('sha256').update(publicKey).digest(); const iv = Buffer.from(ivHex, 'hex'); const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (error) { throw new Error('Decryption failed: ' + error.message); } } /** * Validate passphrase strength */ static validatePassphrase(passphrase) { if (passphrase.length < 12) { return { valid: false, message: 'Passphrase must be at least 12 characters long' }; } if (passphrase.length > 100) { return { valid: false, message: 'Passphrase too long (max 100 characters)' }; } // Check for basic complexity const hasLetter = /[a-zA-Z]/.test(passphrase); const hasNumber = /[0-9]/.test(passphrase); if (!hasLetter || !hasNumber) { return { valid: false, message: 'Passphrase should contain both letters and numbers' }; } return { valid: true }; } /** * Mask a key for display */ static maskKey(key) { if (key.length <= 8) return '*'.repeat(key.length); return key.substring(0, 4) + '*'.repeat(key.length - 8) + key.substring(key.length - 4); } }