Questioning Everything Propaganda

Home Tags
Login RSS
PassGram Summary

PassGram v7.00 — How It Works

Overview

PassGram is a standalone PHP password manager with no external dependencies (no Composer, no SQL database). All data lives in AES-256-GCM encrypted JSON files on disk. The UI is server-side rendered PHP with an OS/2 Warp 3.0 visual theme.


Request Lifecycle

Every HTTP request hits index.php, which:

  1. Boots the autoloader (autoload.php, a minimal PSR-4 implementation).
  2. Starts a secure session (Session::start()).
  3. Loads config from config/config.php and config/security.php.
  4. Reads the Master Application Key (MAK) from config/security.php and constructs the Encryption instance.
  5. Constructs Database, Logger, Validator, and Auth.
  6. Injects the PGP context into $db if the logged-in user has PGP encryption mode active.
  7. Checks authentication — unauthenticated requests for protected pages redirect to login.php.
  8. Routes on ?page= to the appropriate controller.

Encryption Architecture

Layer 1 — Master Application Key (MAK)

Generated once at install. Encrypts all shared JSON databases (users.json.enc, groups.json.enc, invites.json.enc). Stored in config/security.php. Losing it makes all data unrecoverable.

Layer 2 — Authentication

Passwords are hashed with bcrypt (cost 12) for login verification. They are also used to derive a key via PBKDF2-SHA256 (100,000 iterations) for PGP private-key protection.

Layer 3a — AES Credential Mode (default)

Each credential is its own file: data/credentials/{userId}/{prefix}_{title}.json.enc. The file is encrypted with the MAK using AES-256-GCM with a per-operation random 12-byte IV and 16-byte authentication tag. Within that file, sensitive fields (password, custom password fields) are individually re-encrypted with the same key.

Layer 3b — PGP Credential Mode (optional per-user)

The user generates an RSA/DSA/EC key pair. Their private key is stored as data/pgp/{userId}_private.key.enc (AES-256-GCM, passphrase-derived key). When PGP mode is active and the user provides their passphrase at login, $db->setPGPContext() is called and credentials are stored as {prefix}_{title}_gpg.pgp.enc using hybrid RSA+AES-256-GCM: a random session key is encrypted with the user's RSA public key and prepended to the AES ciphertext.

Layer 3c — GnuPG Group Share Mode (optional, requires gnupg_enabled = true)

A separate multi-recipient encryption path implemented in GnuPGEncryption + GroupShare. A single ciphertext is encrypted simultaneously to all group members' GnuPG public keys (one blob, N recipients) and stored in data/group_shares/. Each member decrypts independently with their own private key and passphrase — no shared secret or master key is involved. GroupShare::reEncrypt() re-encrypts for the updated recipient list when group membership changes. This path is implemented but not yet wired to the credential sharing UI; the active sharing path is still the AES/ACL system (Layer 3a). Requires gnupg_enabled = true in config and a GnuPG keyring on the server.

Layer 4 — Export Encryption

Export files (.passgram) use PBKDF2-SHA256 (100,000 iterations) to derive a key from a user-supplied passphrase, then AES-256-GCM-encrypt the credential payload. The passphrase never reaches the server during import — decryption runs in the browser before posting plaintext for re-encryption.


Data Storage

No SQL. All data is encrypted JSON on disk:

Path Contents
data/users.json.enc All user accounts
data/groups.json.enc Group definitions and membership
data/invites.json.enc Invite codes
data/credentials/index.csv Unencrypted fast-lookup index (prefix, filename, user_id, groups, encryption_mode)
data/credentials/{userId}/{prefix}_{title}.json.enc One file per credential (AES mode)
data/credentials/{userId}/{prefix}_{title}_gpg.pgp.enc One file per credential (PGP mode)
data/pgpkeys/{userId}.json.enc User's PGP key store (AES mode)
data/pgpkeys/public_catalog.json.enc Public PGP key directory
data/pgp/{userId}_public.key PEM public key
data/pgp/{userId}_private.key.enc AES-encrypted PEM private key
data/avatars/{userId}.{ext} User avatar images (jpg/png/gif/webp)
data/group_shares/<uuid>.json.enc GnuPG multi-recipient group share packets
data/shares/ PGP-encrypted per-user credential share packets
data/notes/ Encrypted notes
data/logs/ Activity logs

The credentials/index.csv exists so group-share lookups can scan one file instead of opening every user's encrypted credential directory.


Core Features

Authentication & Access Control

  • Invite-only registration — invite codes are tied to a specific group and now carry an assigned role (admin or user, default user). The registrant receives that role automatically.
  • Rate limiting: 5 failed attempts per 15-minute window (per username + IP, tracked in session).
  • Two roles: user and admin. The first registered account is automatically admin.
  • Session stores user_id, username, user_role, logged_in_at, and ip_address.
  • Minimum password length is 3 characters.

Profile Management

Users can view and edit their profile at ?page=profile. The ProfileController provides four actions:

  • show — displays current username, email, bio, and avatar.
  • update — POST; validates and applies username/email/bio changes; updates the session username on rename.
  • upload-avatar — POST; validates image type via getimagesize(), enforces 2 MB limit, stores in data/avatars/{userId}.{ext}.
  • avatar — GET; serves the user's avatar with correct MIME type and cache headers, or an SVG placeholder if no avatar exists.

The username in the navigation bar is a link to the profile page displaying a small circular avatar thumbnail.

Credentials

Credentials support types: password, note, card, identity. Each can have title, username, password, URL, notes, custom fields, tags, and folder. The built-in password generator uses random_int(). Passwords are copied from the list view via an AJAX get-password action that returns only the decrypted password for the targeted item.

The create form uses an explicit Visibility radio section: Personal (private) is the default; selecting Share with group(s) reveals the group multi-select and permission level fields via inline JavaScript.

Groups

Users belong to groups. Group owners can add/remove members, transfer ownership, or delete the group. Groups can be marked require_pgp — members must hold an RSA/EC key pair. The "Add Member" autocomplete filters to eligible users when this flag is set.

Sharing

Group sharing (AES/ACL path — active): the credential owner calls shareWithGroup(), which writes a shared_with_groups entry into the credential's own encrypted file and updates index.csv. Any group member then reads that file directly from the owner's directory. Permissions are read or write.

Group sharing (GnuPG path — implemented, not yet wired): GroupShare::create() encrypts a credential to all group member GnuPG fingerprints simultaneously, storing one ciphertext in data/group_shares/. Requires all members to have imported GnuPG public keys and gnupg_enabled = true in config.

PGP-encrypted per-user sharing: the sender encrypts the credential with the recipient's PGP public key and stores the ciphertext in data/shares/. The recipient decrypts it with their private key.

Public shares: a time-limited unauthenticated link that exposes a single credential without requiring login.

PGP Key Store

Users can import public keys (RSA, DSA, EC) into their personal key store. Keys can be shared with groups or listed in the public, unauthenticated directory at ?page=keys.

Import / Export

  • .passgram export: AES-256-GCM with PBKDF2-derived passphrase key. Strips all internal IDs and sharing metadata; only portable fields travel.
  • GPG agent bridge: for users running a local gpg-agent on localhost:7655, the browser fetches the plaintext from the server and encrypts it via the bridge. PassGram never sees the private key.
  • Import: the browser derives the decryption key from the passphrase client-side, decrypts the file, and posts the plaintext. The server re-encrypts and merges with the user's vault.

Admin Panel

Admins can view all users, suspend accounts, and generate 24-hour password reset tokens. An AuditLog model records all credential and user changes, encrypted like everything else.


Security Controls

Control Implementation
CSRF CSRF class; token checked on every state-changing POST
XSS Sanitizer helper; output escaped with htmlspecialchars()
Rate limiting Session-based, per username+IP
Session hardening httponly, samesite=Strict, secure (configurable)
Password storage bcrypt cost-12 (auth) + PBKDF2-SHA256 (key derivation)
Authenticated encryption AES-256-GCM everywhere — ciphertext is verified before decryption

Tech Stack

  • Language: PHP 7.4+ (PHP 8.x recommended)
  • Crypto: native OpenSSL extension (AES-256-GCM, RSA, DSA, EC key gen); optional php-gnupg extension for GnuPG group shares
  • Storage: encrypted JSON files (no database)
  • Frontend: server-side rendered PHP templates; minimal vanilla JS for copy-to-clipboard, show/hide password, AJAX password fetch, username autocomplete
  • No third-party libraries

Original Author: admin

Views: 41 (Unique: 31)

Page ID ( Copy Link): page_6a0b448854ade7.49603105-99093c09a22ea15f

Page History (2 revisions):