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

End-to-end encryption with AES-256-GCM
Argon2id password hashing with configurable parameters
Auto-lock on system sleep/lock events
Configurable clipboard timeout
Import/export vault functionality
Support for multiple secret types (passwords, API keys, SSH keys, etc.)
Master password change without data loss

Encryption System

The vault uses AES-256-GCM (Galois/Counter Mode) for authenticated encryption, providing both confidentiality and integrity.

Encryption Flow

1.Generate random IV (12 bytes)
2.Encrypt data with AES-256-GCM
3.Extract authentication tag (16 bytes)
4.Concatenate: IV + Encrypted + Tag
5.Encode to base64 for storage
vaultStore.ts
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

Algorithmargon2id
Memory Cost64 MB
Time Cost3 iterations
Hash Length32 bytes

Security Features

  • Unique salt per vault
  • GPU attack resistance
  • Side-channel protection
crypto.ts
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

1Generate random 16-byte salt
2Derive encryption key from password + salt
3Create encrypted verifier
4Save vault.json with salt and verifier

Unlock Existing Vault

vaultUnlock.ts
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.

vault.json structure
{
  "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

unlock(password: string)
getSecrets(password: string)
addSecret(password: string, secret)
editSecret(password: string, secret)
deleteSecret(password: string, secretId)

window.settings

get()
set(settings: AppSettings)
changeMasterPassword(oldPass, newPass)

window.electronAPI

minimize()
maximize()
close()

IPC Handlers

The main process registers IPC handlers to communicate securely with the renderer process.

index.ts (main 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.

Secret Interface
export interface Secret {
  id: string
  name: string
  type: SecretType
  value: string
  username?: string
  url?: string
  notes?: string
  createdAt: Date
  lastAccessed: Date
  category?: string
}
SecretType Union
export type SecretType =
  | 'password'
  | 'api-key'
  | 'ssh-key'
  | 'note'
  | 'totp'
  | 'credit-card'
  | 'bank-account'
  | 'crypto-key'
  | 'software-key'
  | 'wifi-credentials'
  | 'secure-url'
  | 'other'
VaultData Interface
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

autoLockMinutesnumber

Auto-lock timeout in minutes (default: 5)

clipboardSecondsnumber

Clipboard clear timeout in seconds (default: 15)

Default Configuration

{
  "autoLockMinutes": 5,
  "clipboardSeconds": 15
}
Usage Example
// 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

1

Verify old password

Derive key from old password and decrypt verifier

2

Decrypt all secrets

Load and decrypt existing secrets using old key

3

Generate new credentials

Create new salt, derive new key, create new verifier

4

Re-encrypt secrets

Encrypt all secrets with new key

5

Save updated vault

Write new salt, verifier, and encrypted secrets to disk

changeMasterPass.ts
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-screen

System screen locks (Win+L, Control+Command+Q, etc..)

suspend

Computer enters sleep/hibernate mode

minimize

Application window is minimized

index.ts (main process)
// 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')
})
Renderer Process Handler
// 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