A secure, file-based password manager with hierarchical organization. Written in Rust for performance and security.
- Per-item encryption with Argon2id + AES-256-GCM
- Hierarchical organization of secrets
- TOML-based vault format for easy editing and versioning
- Fast filtering of entries
- Clipboard integration with automatic clearing
- Interactive and CLI modes
- Written in Rust for performance and security
- Easy installation via npm or pre-built binaries
- GPG integration for additional vault encryption
- Automatic backups when modifying vault files
- Overwrite protection with user prompts
# Install globally
npm install -g @lucklyric/vaultify
# Or with yarn
yarn global add @lucklyric/vaultify
# Or run directly with npx
npx @lucklyric/vaultifyDownload the latest release for your platform from the releases page.
# Clone the repository
git clone https://github.com/Lucklyric/vaultify.git
cd vaultify
# Build with Rust
cargo build --release
# Binary will be at target/release/vaultifyvaultify initThis creates a vault.toml file in the current directory.
# Will open your system editor for secure input
vaultify add personal/email/gmail -d "Personal Gmail account"
# From stdin
echo "my-secret-password" | vaultify add personal/email/gmail --stdin -d "Personal Gmail"# Simple list
vaultify list
# Tree view
vaultify list --tree
# Filter by scope
vaultify list personal/email# Interactive mode - choose display format after entering password
vaultify decrypt personal/email/gmail
# Show in terminal directly
vaultify decrypt personal/email/gmail --show
# Direct to clipboard (auto-clears after 60 seconds)
vaultify decrypt personal/email/gmail --no-display --clipboardvaultify edit personal/email/gmailvaultify delete personal/email/gmail# Encrypt entire vault with GPG (prompts for backup and ASCII armor)
vaultify gpg-encrypt --recipient [email protected]
# Symmetric encryption (password-based)
vaultify gpg-encrypt
# Create ASCII-armored output (.asc)
vaultify gpg-encrypt --armor
# Decrypt GPG-encrypted vault
vaultify gpg-decryptRun vaultify without any arguments to enter interactive mode:
vaultify
vaultify> help
vaultify> list
vaultify> add work/vpn
vaultify> decrypt work/vpn
vaultify> exitStarting in version 0.4.0, vaultify enforces strict validation rules for scope names to prevent vault file corruption.
Scope names can only contain:
- Letters:
A-Z,a-z - Numbers:
0-9 - Separators:
.(dot) - Special:
-(hyphen),_(underscore)
ASCII Only: No Unicode or special characters allowed for security.
-
Dots separate parts: Use dots to create hierarchy
- Example:
work.email.gmail
- Example:
-
No leading/trailing dots: Must start and end with alphanumeric
- Invalid:
.work- Valid:work - Invalid:
work.- Valid:work
- Invalid:
-
No consecutive dots: Each dot must separate valid parts
- Invalid:
work..email- Valid:work.email
- Invalid:
-
No spaces: Use dots instead
- Invalid:
work email- Valid:work.email
- Invalid:
-
Hyphens/underscores within parts only: Not at boundaries
- Invalid:
-work- Valid:my-work - Invalid:
work-- Valid:my-work - Valid:
my-work.my-email
- Invalid:
-
Maximum length: 256 characters
Check your vault for invalid scopes before migration:
# Check current directory vault
vaultify validate
# Check specific file
vaultify validate path/to/vault.tomlExample output (if invalid):
ERROR: Found 2 invalid scopes in vault file:
Line 5: [work email]
Invalid scope 'work email' at position 5: found space character
Suggestion: Use 'work.email' instead
Line 12: [test..scope]
Invalid scope 'test..scope' at position 5-6: found consecutive dots
Suggestion: Use 'test.scope' instead
For more details, see the migration guide in CHANGELOG.md.
Vaults are stored as TOML files with encrypted content:
version = "v0.4.0"
modified = "2025-10-15T10:00:00Z"
[personal]
description = "Personal accounts"
encrypted = ""
salt = ""
[personal.email]
description = "Email accounts"
encrypted = "base64-encoded-encrypted-content"
salt = "base64-encoded-salt"- Insertion order preserved: Entries maintain their original order
- Smart group insertion: New entries are added at the end of their group
- Native TOML format: Clean, readable structure with dotted key notation
- Flexible parsing: Parent entries are created automatically
Vaultify automatically creates timestamped backups when modifying vault files:
- Vault modifications: Prompts to create backup (default: Yes)
- GPG encryption: Prompts to create backup (default: Yes)
- Backup format:
vault.toml.backup.20250721_152607 - GPG backup format:
vault.toml.gpg.backup.20250721_152607
- Prompts before overwriting existing files
- Applies to backups, GPG encryption, and GPG decryption
- Prevents accidental data loss
- Encryption: Each entry is encrypted with Argon2id + AES-256-GCM
- Per-item salts: Every entry has its own salt for enhanced security
- Memory safety: Written in Rust with automatic memory zeroing
- No password storage: Password required for every operation
- Secure permissions: Vault files are created with 600 permissions
- Backup protection: Automatic backups prevent data loss
VAULT_FILE: Default vault file pathEDITORorVISUAL: System editor for secret input (defaults: notepad on Windows, vi on Unix)
On Unix systems, vault files are automatically created with 600 permissions (read/write for owner only).
This section provides a detailed technical specification of the encryption algorithms used by vaultify. This information allows anyone to implement a compatible decryption tool in any programming language.
Each secret in the vault is independently encrypted using:
- Key Derivation: Argon2id
- Encryption: AES-256-GCM
- Encoding: Base64 for storage
Parameters:
- Memory: 65536 KB (64 MB)
- Iterations: 2
- Parallelism: 1
- Salt length: 16 bytes (128 bits)
- Key length: 32 bytes (256 bits)
Parameters:
- Key: 32 bytes from Argon2id
- Nonce: 12 bytes (96 bits) - randomly generated
- Additional Authenticated Data (AAD): None
- Tag length: 16 bytes (128 bits)
Each encrypted entry in the TOML file contains:
encrypted: Base64-encoded concatenation of [nonce || ciphertext || tag]salt: Base64-encoded 16-byte salt used for Argon2id
To decrypt an entry:
- Parse the TOML file and extract the target entry's
encryptedandsaltfields - Decode from Base64 both fields
- Extract components from the encrypted data:
- Nonce: First 12 bytes
- Ciphertext: Bytes 12 to (length - 16)
- Tag: Last 16 bytes
- Derive key using Argon2id with the decoded salt and user's password
- Decrypt using AES-256-GCM with the derived key, nonce, and tag
- Verify the authentication tag - decryption fails if tag is invalid
import base64
from argon2 import low_level
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def decrypt_vault_entry(encrypted_b64, salt_b64, password):
# Decode from base64
encrypted = base64.b64decode(encrypted_b64)
salt = base64.b64decode(salt_b64)
# Extract components
nonce = encrypted[:12]
ciphertext_with_tag = encrypted[12:]
# Derive key using Argon2id
key = low_level.hash_secret_raw(
secret=password.encode(),
salt=salt,
time_cost=2,
memory_cost=65536,
parallelism=1,
hash_len=32,
type=low_level.Type.ID
)
# Decrypt using AES-256-GCM
aesgcm = AESGCM(key)
plaintext = aesgcm.decrypt(nonce, ciphertext_with_tag, None)
return plaintext.decode('utf-8')- Per-entry encryption: Each secret has its own salt and encryption
- No master key: There's no vault-wide master password
- Forward secrecy: Compromising one entry doesn't affect others
- Authentication: GCM mode provides both encryption and authentication
- Memory hardness: Argon2id resists GPU/ASIC attacks
cargo build --releasecargo test# Format code
cargo fmt
# Run linter
cargo clippyContributions are welcome! Please read our Contributing Guide for details.
Licensed under the Apache License 2.0. See LICENSE for details.