Introduction
This vault application is a secure, Electron-based password manager that uses military-grade encryption to protect your sensitive data. Built with TypeScript, it provides a robust solution for storing passwords, API keys, SSH keys, and other secrets.
AES-256-GCM
Military-grade encryption
Argon2id
Secure key derivation
Zero-Knowledge
Master password never stored
Architecture
The application follows a layered architecture with clear separation between the main process, preload scripts, and renderer process.
Main Process
Handles vault operations, file system access, and IPC communication. Contains core business logic for encryption and secret management.
Preload Layer
Exposes secure APIs to the renderer using contextBridge, preventing direct access to Node.js APIs.
Renderer Process
React-based UI that communicates with main process through the preload API.
Key Features
Encryption System
The vault uses AES-256-GCM (Galois/Counter Mode) for authenticated encryption, providing both confidentiality and integrity.
Encryption Flow
export function encryptSecrets(key: Buffer, secrets: Secret[]): string {
const iv = crypto.randomBytes(12)
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
const encrypted = Buffer.concat([
cipher.update(JSON.stringify(secrets), 'utf-8'),
cipher.final()
])
const tag = cipher.getAuthTag()
return Buffer.concat([iv, encrypted, tag]).toString('base64')
}Key Derivation
Master passwords are transformed into encryption keys using Argon2id, a memory-hard key derivation function resistant to GPU attacks.
Parameters
Security Features
- Unique salt per vault
- GPU attack resistance
- Side-channel protection
export async function deriveKey(password: string, salt: Buffer): Promise<Buffer> {
const key = await argon2.hash(password, {
type: argon2.argon2id,
salt,
hashLength: 32,
raw: true,
memoryCost: 2 ** 16, // 64 MB
timeCost: 3,
parallelism: 1
})
return Buffer.from(key)
}Security Model
The vault implements a zero-knowledge security model where the master password is never stored on disk.
Password Verification
A special verifier string ("vault-check") is encrypted with the derived key. During unlock, the application attempts to decrypt this verifier. Success confirms the correct password.
Encrypted Verifier = AES-256-GCM(key, "vault-check")Runtime Key Management
The encryption key is stored only in memory during the session. It's serialized as a comma-separated string when passed between processes and immediately converted back to a Buffer for cryptographic operations.
Unlock & Setup
The unlockVault function handles both first-time setup and existing vault unlocking.
First-Time Setup
Unlock Existing Vault
const data: VaultData = JSON.parse(fs.readFileSync(VAULT_PATH, 'utf-8'))
const salt = Buffer.from(data.salt, 'base64')
const key = await deriveKey(password, salt)
const verifier = Buffer.from(data.verifier, 'base64')
// Extract IV, tag, and encrypted data from verifier
const iv = verifier.subarray(0, 12)
const tag = verifier.subarray(verifier.length - 16)
const encrypted = verifier.subarray(12, verifier.length - 16)
// Attempt decryption
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
decipher.setAuthTag(tag)
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()])
// Verify password
const unlocked = decrypted.toString() === 'vault-check'Secret Storage
Secrets are stored in an encrypted format within the vault.json file. The file structure includes salt, verifier, and encrypted secrets.
{
"salt": "base64-encoded-16-bytes",
"verifier": "base64-encoded-iv-encrypted-tag",
"secrets": [], // deprecated, kept for backward compatibility
"secretsEncrypted": "base64-encoded-iv-encrypted-secrets-tag"
}Note: The secrets array is cleared after encryption and kept only for backward compatibility. All secrets are stored in the secretsEncrypted field.
CRUD Operations
The vaultStore module provides methods for managing secrets with automatic encryption/decryption.
Get Secrets
getSecrets(password: string)Retrieves and decrypts all secrets from the vault.
const secrets = await vaultStore.getSecrets(sessionKey)
Add Secret
addSecret(password: string, secret: Omit<Secret, "id" | "createdAt" | "lastAccessed">)Adds a new secret with auto-generated ID and timestamps.
const newSecret = await vaultStore.addSecret(sessionKey, {
name: 'GitHub Token',
type: 'api-key',
value: 'ghp_xxxxx...',
username: 'user@example.com'
})Edit Secret
editSecret(password: string, secret: Secret)Updates an existing secret and refreshes lastAccessed timestamp.
await vaultStore.editSecret(sessionKey, updatedSecret)
Delete Secret
deleteSecret(password: string, secretId: string)Removes a secret from the vault permanently.
await vaultStore.deleteSecret(sessionKey, secretId)
Preload API
The preload script exposes secure APIs to the renderer process using Electron's contextBridge.
window.vault
window.settings
window.electronAPI
IPC Handlers
The main process registers IPC handlers to communicate securely with the renderer process.
// Vault operations
ipcMain.handle('vault:unlock', (_, password) => unlockVault(password))
ipcMain.handle('vault:getSecrets', async (_event, password: string) =>
vaultStore.getSecrets(password)
)
ipcMain.handle('vault:addSecret', async (_event, password: string, secret) =>
vaultStore.addSecret(password, secret)
)
ipcMain.handle('vault:editSecret', async (_event, password: string, secret) =>
vaultStore.editSecret(password, secret)
)
ipcMain.handle('vault:deleteSecret', async (_event, password: string, secretId: string) =>
vaultStore.deleteSecret(password, secretId)
)
// Settings operations
ipcMain.handle('settings:get', () => loadSettings())
ipcMain.handle('settings:set', (_, settings) => saveSettings(settings))
ipcMain.handle('settings:changeMasterPass', async (_, oldPass: string, newPass: string) =>
changeMasterPassword(oldPass, newPass)
)
// Window controls
ipcMain.on('window-minimize', () => {
const win = BrowserWindow.getFocusedWindow()
win?.minimize()
})Type Definitions
TypeScript interfaces and types used throughout the application.
export interface Secret {
id: string
name: string
type: SecretType
value: string
username?: string
url?: string
notes?: string
createdAt: Date
lastAccessed: Date
category?: string
}export type SecretType = | 'password' | 'api-key' | 'ssh-key' | 'note' | 'totp' | 'credit-card' | 'bank-account' | 'crypto-key' | 'software-key' | 'wifi-credentials' | 'secure-url' | 'other'
export interface VaultData {
salt: string // base64 encoded
verifier: string // base64 encoded
secrets: Secret[] // legacy, kept for compatibility
secretsEncrypted?: string // base64 encoded AES-256-GCM
}User Settings
Application settings are stored separately from the vault in a settings.json file.
Available Settings
Auto-lock timeout in minutes (default: 5)
Clipboard clear timeout in seconds (default: 15)
Default Configuration
{
"autoLockMinutes": 5,
"clipboardSeconds": 15
}// Load settings
const settings = await window.settings.get()
console.log(settings.autoLockMinutes) // 5
// Update settings
await window.settings.set({
autoLockMinutes: 10,
clipboardSeconds: 30
})Change Master Password
The master password can be changed without losing any data. The process re-encrypts all secrets with a new key derived from the new password.
Password Change Flow
Verify old password
Derive key from old password and decrypt verifier
Decrypt all secrets
Load and decrypt existing secrets using old key
Generate new credentials
Create new salt, derive new key, create new verifier
Re-encrypt secrets
Encrypt all secrets with new key
Save updated vault
Write new salt, verifier, and encrypted secrets to disk
export async function changeMasterPassword(
oldPassword: string,
newPassword: string
): Promise<{ key: Buffer } | null> {
// Verify old password
const oldSalt = Buffer.from(vault.salt, 'base64')
const oldKey = await deriveKey(oldPassword, oldSalt)
// Decrypt secrets with old key
const secrets = decryptSecrets(oldKey, vault.secretsEncrypted)
// Generate new credentials
const newSalt = crypto.randomBytes(16)
const newKey = await deriveKey(newPassword, newSalt)
// Create new verifier
const newVerifier = createVerifier(newKey)
// Re-encrypt with new key
const newSecretsEncrypted = encryptSecrets(newKey, secrets)
// Update vault
vault.salt = newSalt.toString('base64')
vault.verifier = newVerifier.toString('base64')
vault.secretsEncrypted = newSecretsEncrypted
fs.writeFileSync(VAULT_PATH, JSON.stringify(vault, null, 2))
return { key: newKey }
}Import/Export
The vault can be exported for backup purposes or imported from a backup file.
Export Vault
Copies the encrypted vault.json file to your desktop. The exported file remains encrypted and requires the master password to unlock.
const success = await window.vault.downloadVault() // Saves to ~/Desktop/vault.json
Import Vault
Opens a file dialog to select a vault.json backup. Validates JSON format before importing. Warning: Overwrites current vault.
const success = await window.vault.importVault() // Opens file picker dialog
Security Considerations
- • Exported vaults are fully encrypted but should be stored securely
- • Anyone with the vault file and master password can access your secrets
- • Importing a vault permanently replaces your current vault
- • Always verify backup integrity before relying on it for recovery
System Events
The application listens for system events to automatically lock the vault for enhanced security.
Auto-Lock Triggers
lock-screenSystem screen locks (Win+L, Control+Command+Q, etc..)
suspendComputer enters sleep/hibernate mode
minimizeApplication window is minimized
// Listen for system lock events
powerMonitor.on('lock-screen', () => {
mainWindow.webContents.send('system-lock')
})
powerMonitor.on('suspend', () => {
mainWindow.webContents.send('system-lock')
})
mainWindow.on('minimize', () => {
mainWindow.webContents.send('system-lock')
})// In SystemLockHandler
useEffect(() => {
const cleanup = window.electron.onSystemLock(() => {
// Clear session key and lock vault
setSessionKey(null)
setIsLocked(true)
})
return cleanup
}, [])Best Practices
Follow these guidelines to ensure maximum security when using the vault application.
Strong Master Password
- Use a unique password not used elsewhere
- Minimum 12 characters with mixed case, numbers, and symbols
- Consider using a passphrase for better memorability
- Never share your master password
Regular Backups
- Export your vault regularly to a secure location
- Store backups on encrypted external drives
- Test backup restoration periodically
- Keep multiple backup copies in different locations
Security Settings
- Enable auto-lock with a reasonable timeout (5-15 minutes)
- Use clipboard timeout to auto-clear copied secrets
- Lock vault when stepping away from computer
- Keep your operating system and application updated
Secret Management
- Organize secrets with clear, descriptive names
- Add notes with context but avoid sensitive details
- Regularly audit and remove unused secrets
- Use appropriate secret types for better organization