safevibe
Version:
Safevibe CLI - Simple personal secret vault for AI developers and amateur vibe coders
109 lines (108 loc) • 3.57 kB
JavaScript
import { createHash, randomBytes } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
/**
* Generate a new X25519 key pair
* For demo: generates from random bytes
*/
export function generateKeyPair() {
const privateKeyBytes = randomBytes(32);
const publicKeyBytes = randomBytes(32); // In real X25519, this would be derived
return {
privateKey: privateKeyBytes.toString("hex"),
publicKey: publicKeyBytes.toString("hex"),
};
}
/**
* Derive a key pair from a seed (for deterministic generation)
*/
export function deriveKeyPair(seed) {
const hash = createHash("sha256").update(seed).digest();
const privateKey = hash.toString("hex");
// In real X25519, public key would be scalar multiplication
const publicKeyHash = createHash("sha256").update(hash).update("public").digest();
const publicKey = publicKeyHash.toString("hex");
return { privateKey, publicKey };
}
/**
* Encrypt data using the private key
* For demo: simple XOR cipher with nonce
*/
export function encryptData(data, privateKey) {
const nonce = randomBytes(12).toString("hex");
const key = Buffer.from(privateKey, "hex");
const nonceBytes = Buffer.from(nonce, "hex");
// Create encryption key by hashing private key + nonce
const encKey = createHash("sha256")
.update(key)
.update(nonceBytes)
.digest();
// Simple XOR encryption
const dataBytes = Buffer.from(data, "utf-8");
const encrypted = Buffer.alloc(dataBytes.length);
for (let i = 0; i < dataBytes.length; i++) {
encrypted[i] = dataBytes[i] ^ encKey[i % encKey.length];
}
return {
ciphertext: encrypted.toString("base64"),
nonce,
};
}
/**
* Decrypt data using the private key
*/
export function decryptData(ciphertext, nonce, privateKey) {
const key = Buffer.from(privateKey, "hex");
const nonceBytes = Buffer.from(nonce, "hex");
// Recreate encryption key
const encKey = createHash("sha256")
.update(key)
.update(nonceBytes)
.digest();
// Decrypt using XOR
const encryptedBytes = Buffer.from(ciphertext, "base64");
const decrypted = Buffer.alloc(encryptedBytes.length);
for (let i = 0; i < encryptedBytes.length; i++) {
decrypted[i] = encryptedBytes[i] ^ encKey[i % encKey.length];
}
return decrypted.toString("utf-8");
}
/**
* Hash a secret name for storage
*/
export function hashSecretName(name) {
return createHash("sha256").update(name).digest("hex");
}
/**
* Save key pair to file securely
*/
export async function saveKeyPair(keyPair, filePath) {
const keyData = {
publicKey: keyPair.publicKey,
privateKey: keyPair.privateKey,
generated: new Date().toISOString(),
algorithm: "demo-x25519", // In production: "x25519"
};
await writeFile(filePath, JSON.stringify(keyData, null, 2), { mode: 0o600 });
}
/**
* Load key pair from file
*/
export async function loadKeyPair(filePath) {
const keyData = JSON.parse(await readFile(filePath, "utf-8"));
if (!keyData.publicKey || !keyData.privateKey) {
throw new Error("Invalid key file format");
}
return {
publicKey: keyData.publicKey,
privateKey: keyData.privateKey,
};
}
/**
* Generate a deterministic project key from user key + project ID
*/
export function deriveProjectKey(userPrivateKey, projectId) {
return createHash("sha256")
.update(Buffer.from(userPrivateKey, "hex"))
.update(projectId)
.digest("hex");
}