Afzan Khan
afzanlearns
Back to Projects
PythonAES-256-GCMSQLiteCLI

Aegis

About the Project

A local-only, no-account, no-session-timeout CLI for storing and retrieving encrypted secrets — built deliberately minimal in reaction to how over-engineered most secrets managers have become.

Overview

Aegis starts from a simple premise: most secrets managers add complexity — cloud sync, session timeouts, recovery codes, sprawling command sets — to solve problems that a developer writing down a master password already solves on their own. Aegis strips all of that away to seven commands and one core idea: every secret is encrypted at rest with AES-256-GCM, unlocked by a single master password, and stored entirely locally in ~/.aegis/ — nothing ever leaves the machine, and nothing beyond a password hash and encrypted values ever touches disk.

Security Architecture

The master password is never stored anywhere. On setup, it's run through Argon2id (64 MB memory, 2 iterations, 8 lanes — deliberately memory-hard and GPU-resistant) to derive a 256-bit encryption key, which is used for AES-256-GCM encryption of every secret with a unique random nonce per entry. A separate PBKDF2-SHA256 hash of the password is stored purely for login verification, compared in constant time to avoid timing attacks. On login, the derived key is written to a session file (~/.aegis/.session, 600 permissions) that persists indefinitely until explicit logout — no arbitrary 30-minute timeout forcing re-authentication mid-work, but also no session surviving a new terminal, since the file is scoped to the login it was created in.

Core Features

  • AES-256-GCM encryption — authenticated encryption with a unique nonce per secret, preventing both tampering and nonce reuse
  • Argon2id key derivation — memory-hard KDF resistant to GPU-based brute-forcing
  • Tag-based organization — every secret carries a tag (password, api, database, etc.) and can be fetched filtered by tag
  • Secure password generation — cryptographically random passwords with a full symbol set, configurable length
  • No cloud, no sync, no recovery codes — the entire vault is a local SQLite database (~/.aegis/vault.db); losing the password means losing the vault, by design
  • Minimal command surfacesetup, login, logout, save, fetch, list, delete, generate — no flags to memorize, no configuration file to maintain

What Gets Stored (and What Doesn't)

StoredNever stored
PBKDF2 password hashThe master password itself
AES-256-GCM encrypted secret valuesPlaintext secret values
Derived session key (while logged in)Session key after logout (file is deleted)

Both the vault database and session file are written with 600 permissions (owner read/write only).

Project Structure

The codebase separates concerns cleanly across core/ (crypto, key derivation, the central vault orchestrator), security/ (clipboard handling with auto-clear, memory wiping via ctypes, file permission enforcement), storage/ (a thin SQLite wrapper over a two-table schema — vault_config and secrets), and cli/ (Click-based command registration with Rich-formatted terminal output).

Database Schema

CREATE TABLE vault_config (
    id TEXT PRIMARY KEY DEFAULT 'default',
    password_hash TEXT NOT NULL,
    password_salt TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE secrets (
    id TEXT PRIMARY KEY,
    name TEXT UNIQUE NOT NULL,
    encrypted_value TEXT NOT NULL,
    nonce TEXT NOT NULL,
    tag TEXT DEFAULT 'general',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Tech Stack

Python 3.10+ · Click · Rich (terminal formatting) · Argon2id + PBKDF2-SHA256 · AES-256-GCM · SQLite

Usage

aegis setup                                    # create vault, set master password
aegis save emailpassword P@ssw0rd! password     # save a tagged secret
aegis fetch password                             # fetch all secrets tagged 'password'
aegis generate 24                                 # generate a secure random password
aegis logout                                       # clear the session