Case Study: Engineering a Zero-Trust, Cross-Platform Local Password Manager
This case study analyzes the architecture, cryptographic design, and implementation challenges of LockRoot. It documents the transition to the v2 vault format, the rigorous requirements of cross-platform Authenticated Encryption with Associated Data (AEAD), and the complexities of securely managing memory lifetimes across managed languages (Kotlin, Swift, and C#).
1. Introduction
The modern password manager ecosystem is dominated by cloud-first architectures. Solutions like 1Password, Bitwarden, and LastPass prioritize synchronization, multi-device accessibility, and account recovery. While convenient, this architecture inherently introduces an external trust boundary: the cloud provider.
Cloud-based vaults expose users to a unique class of systemic risks. Centralized credential stores represent high-value targets for advanced persistent threats (APTs). A single breach of the provider’s infrastructure can expose millions of encrypted vaults to offline brute-forcing attacks. Furthermore, web-based clients and auto-updating desktop applications introduce supply chain vulnerabilities; malicious JavaScript can be injected by a compromised CDN to silently exfiltrate plaintext passwords as the user unlocks their vault.
This case study details the engineering of LockRoot, a stark departure from the cloud-first paradigm. LockRoot is a strictly local, offline-first password manager engineered for Android, iOS, Windows, Linux, and macOS. It was built with a singular design requirement: the vault lives exclusively on the local device, and the software makes absolutely zero network connections.
2. Problem Statement
Building a secure, local-only password manager presents distinct engineering challenges that cloud-based managers often offload to their servers or bypass entirely.
2.1 The Lack of Server-Side Rate Limiting
In a cloud-first architecture, the server enforces rate limiting on login attempts. If an attacker tries 10 incorrect master passwords, the server can temporarily lock the account or demand multi-factor authentication (MFA).
In an offline architecture, there is no server to enforce rate limiting. An attacker who gains physical access to the device (or steals the encrypted vault file) can mount an offline brute-force attack. They can attempt millions of passwords per second using parallelized GPU clusters without any system intervening to stop them.
2.2 Cross-Platform Payload Serialization
LockRoot must operate natively across five distinct operating systems utilizing three different programming languages (Kotlin on Android, Swift on iOS/macOS, C# on Windows/Linux). Ensuring that a vault file exported from an Android device can be seamlessly imported, decrypted, and parsed by the Windows WPF application requires a rigorously defined, byte-for-byte compatible serialization format and identical cryptographic behavior across entirely different cryptographic libraries (BouncyCastle, CryptoKit, and .NET AesGcm).
2.3 Memory Forensics in Managed Languages
A critical vulnerability window occurs while the vault is unlocked. The master password must be temporarily held in memory to derive the decryption key. In lower-level languages like C or Rust, memory can be deterministically zeroed (e.g., using explicit_bzero) immediately after use.
However, LockRoot is built using high-level, garbage-collected (GC) or reference-counted languages. If a user types their password into a standard Swift String or Java String, the runtime allocates that string on the heap. The string is immutable; it cannot be securely overwritten. When it falls out of scope, it remains in memory until the GC decides to sweep it. If an attacker dumps the application’s RAM during this window, the plaintext master password can be extracted.
3. Background and Threat Model
LockRoot operates under a strict, well-defined threat model that delineates what the software can technically protect against and where the user’s operational security must take over.
3.1 The Trust Boundary
LockRoot trusts the host operating system, the hardware, and the user. It explicitly does not trust the local filesystem (hence AES-256-GCM encryption) or any transport channel used to move exports between devices (hence encrypted exports).
3.2 In-Scope Defenses
- Offline Brute Force: A stolen encrypted vault file is protected against rapid cracking via a memory-hard Key Derivation Function (KDF).
- Envelope Tampering: Modifying the KDF iteration count in the JSON envelope to intentionally weaken derivation is detected by the AEAD tag.
- Passive Keylogging via Clipboard: A 20-second clipboard eviction timer mitigates exposure to malicious apps silently polling the system clipboard.
- Over-the-Shoulder Sniping: Auto-locking on inactivity (Windows/Linux) or backgrounding (iOS) limits exposure windows in public physical spaces.
3.3 Out-of-Scope Risks
- Compromised/Rooted Devices: LockRoot cannot protect data in RAM from a privileged process (e.g., malware utilizing
ptraceor rooting tools). - Active Keyloggers: If a malicious software keyboard captures the master password during initial entry, the vault is compromised before encryption occurs.
- Lost Passwords: There is no recovery mechanism. No recovery key, no admin reset. If the password is lost, the vault is mathematically inaccessible.
4. Architecture and Cryptographic Design
The core of LockRoot is its cryptographic pipeline. The pipeline must take a user-supplied string (the master password) and securely transform it into a 256-bit key used to encrypt the local JSON payload.
4.1 Key Derivation: Argon2id
To mitigate the offline brute-force threat (Section 2.1), LockRoot utilizes Argon2id (RFC 9106, version 1.3). Argon2id is a memory-hard KDF that combines the data-dependent memory access of Argon2d (resisting GPU cracking) with the data-independent access of Argon2i (resisting side-channel timing attacks).
Parameter Selection:
- Memory Cost: 64 MiB (65,536 KiB)
- Time Cost (Iterations): 3
- Parallelism (Lanes): 2
- Salt: 32 bytes (randomly generated via a Cryptographically Secure Pseudorandom Number Generator (CSPRNG) for every new vault and export).
Benchmarking and Rationale: These specific parameters were chosen through rigorous performance profiling on low-to-mid-range mobile hardware (e.g., older Android ARMv8 devices). The goal was to target a derivation execution time of roughly 500ms to 1000ms.
This half-second delay is imperceptible to a human user logging in once per session. However, for an attacker attempting to brute-force a stolen vault on an RTX 4090 GPU, the 64 MiB memory requirement per hash severely limits parallelization. Unlike PBKDF2 or SHA-256, which require negligible memory and allow a GPU to calculate millions of hashes per second, Argon2id creates a massive memory bandwidth bottleneck, drastically reducing the attacker’s hash rate and making brute-forcing a 12-character password mathematically unfeasible.
4.2 Authenticated Encryption (AES-256-GCM)
The derived 256-bit key is used to encrypt the vault payload using AES-256-GCM.
AES-GCM is an Authenticated Encryption with Associated Data (AEAD) cipher. It provides both confidentiality (encrypting the data) and authenticity (ensuring the data hasn’t been tampered with).
The V2 Envelope Format: LockRoot standardizes all vault files into a UTF-8 encoded JSON envelope.
{
"magic": "Lockroot_VAULT",
"version": 2,
"kdf": {
"name": "argon2id",
"memory": 65536,
"iterations": 3,
"parallelism": 2,
"salt": "<base64>"
},
"cipher": {
"name": "aes-256-gcm",
"nonce": "<base64>"
},
"ciphertext": "<base64>",
"tag": "<base64>"
}
4.3 Binding Metadata via Associated Data (AAD)
A critical security feature of AEAD is the ability to authenticate plaintext metadata alongside the ciphertext. If an attacker modifies the JSON envelope to reduce the Argon2id iterations from 3 to 1 (hoping to speed up their brute-force attack), the decryption must fail entirely.
To enforce this, LockRoot constructs a pipe-delimited UTF-8 string containing all critical envelope fields:
Lockroot_VAULT|2|argon2id|65536|3|2|<salt_b64>|aes-256-gcm|<nonce_b64>
This string is passed as the Associated Data to the AES-GCM cipher during encryption. During decryption, the app reconstructs this string from the envelope. If a single bit of the KDF parameters or the cipher nonce has been altered, the calculated AEAD tag will not match the stored tag, and the decryption throws an authentication failure immediately, prior to returning any plaintext.
5. Implementation: Mitigating Memory Forensics
As identified in Section 2.3, securely wiping the master password from memory in managed languages requires specific, platform-by-platform engineering to bypass garbage collection and compiler optimizations.
5.1 Android (Kotlin) Implementation
In the Android app/ module, the master password is never collected into an immutable Java String. Instead, the UI layer extracts it directly into a mutable CharArray.
When the key derivation is complete, the CryptoService.kt implementation must zero this array. However, modern JVM Just-In-Time (JIT) compilers perform dead-code elimination. If the compiler sees Arrays.fill(bytes, 0) at the end of a method, and the bytes array is never read again, the compiler optimizes the instruction away to save CPU cycles, leaving the password in RAM.
To defeat the JIT compiler, LockRoot implements a @Volatile sink:
// app/src/main/java/com/regaan/lockroot/crypto/CryptoService.kt
@Volatile private var wipeSink: Int = 0
fun wipe(bytes: ByteArray?) {
if (bytes == null) return
secureRandom.nextBytes(bytes) // Randomize first
Arrays.fill(bytes, 0.toByte()) // Zero explicitly
wipeSink = bytes.hashCode() // Force the JVM to evaluate the array
}
By assigning a calculation based on the zeroed array to a volatile variable, the JVM is forced to execute the Arrays.fill instruction, guaranteeing memory erasure.
5.2 iOS and macOS (Swift) Implementation
Swift strings are immutable and cannot be safely zeroed. The iOS VaultViewModel.swift immediately converts the user input into a raw Data object at the earliest possible boundary.
To wipe this Data object, LockRoot leverages the C-based libsodium library, specifically sodium_memzero, which is guaranteed not to be optimized away by the LLVM compiler.
To ensure the wipe occurs even if the derivation function throws an error, Swift’s defer statement is utilized:
// ios/Lockroot/Lockroot/Crypto/CryptoService.swift
func processPassword(input: inout Data) {
defer {
input.withUnsafeMutableBytes { raw in
guard let baseAddress = raw.baseAddress else { return }
sodium_memzero(baseAddress, raw.count)
}
}
// Perform Argon2id hash...
}
5.3 Windows and Linux (C#) Implementation
On the desktop platforms, the .NET 8 framework provides specific primitives for secure memory handling. The UI binds the password to a SecureString or passes it as a ReadOnlySpan<char>. Wiping is handled explicitly via CryptographicOperations.ZeroMemory(), a native CLR call that the C# compiler is strictly forbidden from optimizing away.
6. Implementation: Security Boundaries
Beyond cryptography, LockRoot relies on OS-level APIs to enforce security boundaries around the app’s execution context.
6.1 Mitigating Screen Capture
Malware often relies on taking silent screenshots or recording the screen while the user views their vault.
- Android: In
MainActivity.kt, the application executeswindow.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE). This instructs the Android surface flinger to redact the app’s contents from screenshots and the recent-apps task switcher. - Windows: The WPF application utilizes the Win32
SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE)API, turning the application window black in screenshots and screen sharing software (like OBS or Teams). - iOS/Linux: These operating systems lack granular, app-level screenshot blocking APIs. LockRoot relies on aggressive auto-locking to minimize the exposure window.
6.2 Managing the Clipboard Lifecycle
Users frequently copy passwords to paste them into browsers. If left in the clipboard, any background app can read them.
On iOS (VaultViewModel.swift), LockRoot implements a timed eviction task utilizing Swift Concurrency:
func copyToClipboard(secret: String) {
UIPasteboard.general.string = secret
let changeCount = UIPasteboard.general.changeCount
Task {
try? await Task.sleep(nanoseconds: 20_000_000_000) // 20 seconds
// Only clear if the user hasn't manually copied something else
if UIPasteboard.general.changeCount == changeCount {
UIPasteboard.general.items = []
}
}
}
A similar ClipboardGuard.kt utility handles this on Android, falling back to writing an empty string (ClipData.newPlainText("", "")) on older OS versions where clearPrimaryClip() is unavailable.
6.3 Background State Management
When a user switches away from the application, the vault must be secured immediately.
On iOS, LockrootApp.swift monitors the environment’s scenePhase. When the phase shifts to .inactive or .background, the app synchronously triggers viewModel.lockForBackground(). This drops the AES key references, purges the decrypted entries from the UI state, and forces the user to re-enter their master password upon return.
7. The V1 to V2 Migration: Legacy Support Challenges
LockRoot originally implemented XChaCha20-Poly1305 on mobile platforms due to its excellent performance on older ARM chips lacking hardware AES acceleration. However, maintaining two separate cryptographic implementations across five platforms created significant technical debt. The desktop applications (Windows/Linux) exclusively used AES-256-GCM.
To unify the ecosystem, LockRoot mandated a transition to a universal AES-256-GCM V2 envelope format.
7.1 The One-Way Migration Logic
If an Android user updates their app, they still need to decrypt their old XChaCha20 vault. The mobile apps bundle a legacy reader utilizing lazysodium.
Upon a successful unlock, the vault codec evaluates three conditions:
needsMigration = (magic != "Lockroot_VAULT") || (version != 2) || (cipher != "aes-256-gcm")
If true, the application immediately re-encrypts the plaintext vault data using the newly derived key, this time generating a fresh AES-GCM nonce and formatting the file into the V2 schema. The legacy file is overwritten. Because the key is already in memory, this migration happens transparently to the user, requiring no secondary authentication prompt. Desktop platforms perform the same logic but only migrate older AES-GCM V1 files, as they lack the XChaCha20 decryption bindings.
8. Defeating Denial-of-Service via Input Validation
The V2 vault format stores the Argon2id KDF parameters (memory, iterations) in plaintext within the JSON envelope. This is necessary because the application must know how to configure the KDF before it can derive the key to decrypt the vault.
This creates a Denial-of-Service (DoS) vector. A malicious actor could provide the user with a crafted vault file where the JSON envelope specifies memory: 8,388,608 (8 GiB) and iterations: 1000. When the user attempts to unlock this file, the app would read these parameters, allocate 8 GiB of RAM, and lock up the device’s CPU for hours, ultimately causing the OS to kill the app via an Out-of-Memory (OOM) exception.
To prevent this, the vault parser enforces strict parameter bounds before initiating the cryptographic pipeline:
- Memory (KiB): Min
19,456, Max262,144(256 MiB) - Iterations: Min
2, Max10 - Parallelism: Min
1, Max8
If a vault file requests parameters outside these bounds, the parser immediately throws a SecurityException and aborts the unlock flow, neutralizing the DoS attack.
9. Conclusion
LockRoot demonstrates that a highly secure, privacy-respecting password manager can be built without relying on cloud infrastructure. By tightly integrating modern cryptographic primitives (Argon2id, AES-256-GCM) with platform-specific security boundaries (FLAG_SECURE, sodium_memzero, .completeFileProtection), LockRoot provides a robust defense against offline brute-forcing, physical device theft, and malicious software surveillance.
The transition to the unified V2 vault format ensures seamless interoperability across Android, iOS, Windows, Linux, and macOS, proving that a zero-trust, local-only architecture can deliver the same cross-device usability as commercial cloud offerings, without the systemic risks of centralized credential storage.
REGAAN R