A cloud-based password manager — Secure, Simple, and Self-Hosted
LibrePass is a self-hosted, cloud-based password manager designed with security and simplicity in mind. It provides end-to-end encrypted password storage and synchronization across multiple devices while maintaining full control over your data.
This is a hobby project that served as a comprehensive learning experience in building secure systems from mobile clients to cloud backends. The core, completed parts are the Server (Kotlin/Spring Boot) and Android (Kotlin/Compose); other clients were started but not completed.
Client Layer (Android)
- Android app with Kotlin & Jetpack Compose
- End-to-end encryption on client-side (AES-256-GCM, Argon2id, X25519)
- Partial offline support (Room
needUploadqueue for add/view; login/delete require network)
API Layer (Spring Boot Backend)
- RESTful endpoints for vault operations (
/api/auth,/api/cipher/sync,/api/collection,/api/user) - Opaque bearer token auth (
lp_+ 32B, stored in DB — no JWT) - Basic validation + Bucket4j rate limiting on Auth/Cipher
- TLS terminated at reverse proxy (Caddy in production)
Data Layer (PostgreSQL / H2)
- User accounts: email, Argon2 params + X25519
publicKey(no password/hash on server) - Encrypted vault data (
protectedDatahex, server never sees plaintext) lastServerSynctimestamps for sync (last-write-wins, no merge)
User Registration/Login
Client: Argon2id(password, email) → X25519 private → publicKey
Client → Server (HTTPS via Caddy): email, publicKey, sharedKey (X25519 with ServerPublicKey), argon2 params
Server: validate sharedKey (HSAuth V1), store user, generate opaque token
Server → Client: token
Password Storage & Sync
Client: derive AES-GCM key (self-ECDH: X25519 private ↔ publicFromPrivate), encrypt vault
Client → Server (HTTPS): encrypted payload
Server: store encrypted data
Server → Client: SyncResponse with changes since lastSyncTimestamp
Client: decrypt & update local vault (Room)
Offline Synchronization
Client (Offline): store view/add in Room (needUpload=true); login/delete not queued (known limitation)
Client (Online): POST /api/cipher/sync {lastSyncTimestamp, updated[], deleted[]}
Server: delete + save (last-write-wins, no conflict merge) → return ciphers with lastServerSync > lastSyncTimestamp
Client: update local DB
| Platform | Status | Technology | Key Features |
|---|---|---|---|
| 🤖 Android | ✅ Complete | Kotlin, Jetpack Compose | Biometric, Material 3, Sync |
| 🔧 Backend | ✅ Complete | Spring Boot 3.3.2, Kotlin 2.0, PostgreSQL | REST APIs, Opaque Token Auth, E2E (AES-GCM) |
| 📚 Docs | ✅ Reference | Markdown, MkDocs Material | Setup, API, Self-hosting (Caddy) |
| Repository | Language | Status | Description |
|---|---|---|---|
| LibrePass-Server | Kotlin/Java | Unmaintained | Spring Boot backend, modular monolith (server/shared/client), opaque token auth, Caddy TLS |
| LibrePass-Android | Kotlin | Unmaintained | Android client with Jetpack Compose, biometric auth, Material 3, partial offline |
| LibrePass-Docs | Markdown | Reference | Setup guides, API docs, self-hosting with Docker + Caddy |
Other repositories in the org (
Desktop,iOS,Browser,Website,JS-API) were experimental/incomplete and are archived as-is — not highlighted here.
- ✅ End-to-End Encryption — Client-side AES-256-GCM (Argon2id → X25519 → AES-GCM); server stores only ciphertext
- ✅ Secure Authentication — Opaque bearer tokens in DB + X25519/HSAuth V1 handshake (server keypair at startup
Auth.kt:44) - ✅ Biometric Unlock — Face/fingerprint (Android
BIOMETRIC_STRONG) - ✅ Automatic Vault Lock — Configurable timeout
- ✅ HTTPS/TLS — Terminated at Caddy/NGINX/Cloudflare in front of app (
HTTP_IP_HEADER=X-Forwarded-Forfor rate limiting)
- ✅ Cross-Device Synchronization — Timestamp-based
POST /api/cipher/sync - ✅ Self-Hosted — Docker Compose (
librepass:8080+postgres), full control over data - ✅ Partial Offline Support — Room cache for view/add; sync on reconnect (no conflict merge)
- ✅ TOTP — Time-based One-Time Passwords (
dev.medzik:otp:1.0.1)
- ✅ Material 3 Design — Native Android 12+ dynamic colors
- ✅ Dark Mode — Full dark mode support
- ✅ Multi-Language — Community-contributed translations
- ✅ Password Generation — Customizable generator (known limitation:
java.util.Random, notSecureRandom— see Android README)
Spring Boot 3.3.2 → Kotlin 2.0 / JDK 21 → Spring Data JPA → PostgreSQL / H2
Opaque Token (DB) → libcrypto 1.2.0 (AES-GCM/X25519/Argon2id) + hsauth 1.0.0 + otp 1.0.1
RESTful APIs → Bucket4j 8.10.1 (Auth 20/min, Cipher 200/min) → Docker multi-stage + Caddy
Kotlin 2.0 / Coroutines 1.8.1 → Jetpack Compose 1.6.8 → Dagger Hilt 2.51.1 → Biometric API
Material 3 + Navigation → Room 2.6.1 + DataStore 1.1.1 → E2E (AES-GCM via libcrypto)
- Core Repositories: 3 (Server, Android, Docs) + archived experiments
- Primary Language: Kotlin + Java
- Architecture: Multi-module, modular monolith (not microservices), single-instance self-hosted
- License: GNU Affero General Public License v3.0
- Last Updated: October 2024 (Unmaintained)
- Learning Focus: Security, Mobile Dev, Backend Systems, Cloud Sync, Docker + Caddy
For detailed information about each component:
- Server Setup: LibrePass-Server README
- Android App: LibrePass-Android README
- Full Docs: LibrePass-Docs (
docs/self-hosting.mdfor Docker + Caddy +.envvariables)
This project is no longer actively maintained as of October 2024. It was developed as a hobby/learning project and has served its purpose as a comprehensive educational resource.
- ✅ LibrePass-Server — Fully implemented and functional (single-instance,
ddl-auto=update— no Flyway, known limitation) - ✅ LibrePass-Android — Feature-complete mobile client (partial offline, known
Random/Log.iissues documented) - 📖 LibrePass-Docs — Reference documentation
The codebase remains available as:
- ✅ A reference implementation for secure password managers (with documented trade-offs)
- ✅ A learning resource for backend and mobile development
- ✅ An example of clean architecture and security best practices (and their limits in a hobby project)
- ✅ An open-source contribution
- ✅ A case study in project scope and time management
This backend implements several security measures:
- End-to-End Encryption — Vaults encrypted client-side with AES-256-GCM before transmission; server never sees plaintext
- Secure Authentication — Opaque bearer tokens (
lp_+ 32B) stored in DB; no JWT, no refresh — tokens valid until deleted (30d inactivity cleanup) - HTTPS via Reverse Proxy — TLS terminated at Caddy (production) in front of app (app serves HTTP
:8080); setHTTP_IP_HEADER=X-Forwarded-Forwhen behind proxy - Password Hashing — Client-side Argon2id (email as salt); server stores only
publicKey+ params, never password - No Plaintext Storage — Only encrypted
protectedDatahex in DB - Input Validation — Basic
hexValidator+ Spring Validation (length, email); not exhaustive - Rate Limiting — Bucket4j on Auth/Cipher; some endpoints not covered — hardening opportunity
Note: This is a hobby project. For production use, consider professional audits, DB migrations, indexes, full rate-limit coverage, and replacing
java.util.RandomwithSecureRandomin Android generator.
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0)
This means:
- ✅ You can use, modify, and distribute the code
- ✅ You must disclose modifications
- ✅ You must use the same license
- 📖 Read Full License
- LibrePass Android — Mobile client for Android
- LibrePass Server — Backend (Spring Boot, Caddy TLS)
This hobby project taught me invaluable lessons about:
- 🏗️ Building Spring Boot applications with modular monolith and multi-module architecture
- 🔑 Secure authentication with opaque token management and database storage (vs. JWT)
- 💾 Database design for encrypted data storage (and limits of
ddl-auto=update) - 🔄 Timestamp-based sync (
lastSyncTimestamp) with last-write-wins (no merge) - 📡 RESTful API design (and why
/api/v1versioning was not added) - 🐳 Docker multi-stage builds and Caddy reverse proxy deployment (
X-Forwarded-For)
- 📱 Modern Kotlin and Jetpack Compose UI frameworks
- 🔐 Biometric authentication integration
- 🎨 Material Design 3 implementation with dynamic theming
- 🏗️ Clean architecture and multi-module project structure
- 🔄 Data synchronization in mobile apps (partial offline with
needUpload) - 📍 Offline-first trade-offs (queue for add/view, network required for login/delete)
- 🔒 End-to-end encryption (client-side Argon2id → X25519 → AES-256-GCM)
- 🔑 Secure password handling (client-side hashing, server never sees secret)
- 🛡️ HTTPS/TLS via reverse proxy and proxy-aware rate limiting
- 🎯 Biometric security best practices
- 🚨 Input validation and its limits
- 📊 Rate limiting with Bucket4j
- 🐳 Multi-stage Docker builds
- 📦 Maven and dependency management
- 🔧 Environment configuration via
spring-dotenvand.env.schema - 📝 Documentation and knowledge sharing
- 🏛️ Modular monolith (not microservices) and separation of concerns
- 🔄 Cloud synchronization with last-write-wins
- 📊 Data consistency without distributed conflict resolution
- 🎯 SOLID principles
- 🧪 Testing strategies (
sharedhas tests,servertests skipped)
This project was a comprehensive learning experience that combined practical backend development with real-world security challenges and the trade-offs of a hobby self-hosted deployment.
Oskar Karpiński (@oskarkarpinski.eu)
With contributions from the LibrePass community.
- 🌐 Website: oskarkarpinski.eu
- 📧 Email: karpinski.oskar@outlook.com
- 🐙 GitHub: @OskarKarpinski
A comprehensive journey through secure software development, from mobile clients to cloud backends



