// Vault: password-based encryption of secrets using libsodium. // Uses Argon2id for key derivation and XSalsa20-Poly1305 for encryption. // All crypto operations are delegated to libsodium — no raw primitives. const sodium = require("libsodium-wrappers-sumo"); let ready = false; async function ensureReady() { if (!ready) { await sodium.ready; ready = true; } } // Returns { salt, nonce, ciphertext } (all base64-encoded strings). async function encryptWithPassword(plaintext, password) { await ensureReady(); const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES); const key = sodium.crypto_pwhash( sodium.crypto_secretbox_KEYBYTES, password, salt, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, sodium.crypto_pwhash_ALG_ARGON2ID13, ); const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); const ciphertext = sodium.crypto_secretbox_easy( sodium.from_string(plaintext), nonce, key, ); return { salt: sodium.to_base64(salt), nonce: sodium.to_base64(nonce), ciphertext: sodium.to_base64(ciphertext), }; } // Returns the plaintext string, or throws on wrong password. async function decryptWithPassword(encrypted, password) { await ensureReady(); const salt = sodium.from_base64(encrypted.salt); const nonce = sodium.from_base64(encrypted.nonce); const ciphertext = sodium.from_base64(encrypted.ciphertext); const key = sodium.crypto_pwhash( sodium.crypto_secretbox_KEYBYTES, password, salt, sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, sodium.crypto_pwhash_ALG_ARGON2ID13, ); const plaintext = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key); if (!plaintext) { throw new Error("Decryption failed — wrong password."); } return sodium.to_string(plaintext); } module.exports = { encryptWithPassword, decryptWithPassword };