DEV Community

Cover image for I Built a B2B Password Manager in Rust
Pascal Kuhn
Pascal Kuhn

Posted on

I Built a B2B Password Manager in Rust

πŸ’‘ TL;DR: I built a file-based B2B password manager in Rust with multi-user encryption (DEK wrapping, no server required).

A practical look at building secure multi-user encryption without a server.

At our office, passwords lived in a shared Excel file.

Yeah..., I know.

But if we're being realistic, most people have seen worse.

I started looking at alternatives.

Bitwarden is great, but it wants a server.
KeePass works well, but multi-user support never really felt like a first-class use case.
LastPass had… its moment.
And 1Password means subscriptions and trusting the cloud.

What I wanted felt surprisingly simple:

An encrypted file sitting on a file server.
No Docker. No VPS. No subscription.

Just a vault multiple people could access β€” each with their own password.

So I built it.


Why Rust Ended Up Being the Right Choice

I could have built this faster in Python or Go.

But a password manager that leaves secrets hanging around in memory isn't a password manager β€” it's a liability.

That’s where Rust made sense.

Its ownership model removes an entire class of memory-related mistakes from the equation.

The zeroize crate quickly became essential. Every sensitive buffer gets explicitly wiped when it leaves scope:

let master_key: Zeroizing<Vec<u8>> = derive_key(&password)?;
// Automatically wiped when dropped
Enter fullscreen mode Exit fullscreen mode

The Multi-User Problem Is Harder Than It Looks

Single-user encryption is relatively straightforward.

Multi-user encryption without a server gets interesting fast.

The obvious approach is one shared master password.

Which sounds convenient β€” until someone leaves the company.

Now you're rotating passwords, re-encrypting data, distributing credentials, and inevitably doing it at the worst possible time.

So I ended up with DEK wrapping.

Vault structure

Vault payload
└── Encrypted using DEK (random, never changes)

Each user:
└── KEK derived from user password (Argon2id)

└── DEK wrapped with that KEK
Enter fullscreen mode Exit fullscreen mode

Each person has their own password.

Removing a user means deleting their wrapped DEK entry.

The vault itself never needs to be re-encrypted.

Getting there took three format versions.

Not ideal β€” but at least the migration code survived.


Tauri Was the Right Choice β€” Mostly

One thing I didn't expect:

The security boundary became incredibly clean.

The React frontend never touches key material directly.

Secrets move through a single command with rate limiting in place:

#[tauri::command]
pub async fn reveal_secret(
    state: State<'_, AppState>,
    entry_id: String,
) -> Result<String, String> {
    rate_limit_check(&state)?; // 5 requests per 60 seconds
    ensure_vault_unlocked(&state)?;
}
Enter fullscreen mode Exit fullscreen mode

The browser extension via Native Messaging turned out to be harder.

Keeping the connection alive without creating focus loops while the app was minimized to the system tray took three complete rewrites.


The Unexpected PDF Detour

I needed PDF exports for GDPR compliance reports.

First attempt: printpdf.

Two days later: a high-severity vulnerability in lopdf, no upstream fix.

Second attempt: jsPDF in the frontend.

Client-side. Offline. UTF-8 behaved properly. Output looked professional.

In hindsight, I should have started there.

Lesson learned:

Use the simplest solution first β€” especially for things that aren't your core product.


What I Got Wrong

I wrote everything in German first

My target market was DACH, so it felt reasonable.

The first comment on r/rust:

"readme.de.md β€” very few people here speak German."

So this week I translated everything.


I built too many features before talking to users

SSH Quick Connect.
Git sync.
GPO policy management.

All before having a single real customer.

Some of it was genuinely fun to build.

Whether anyone actually needs it is still an open question.


I underestimated the system tray

It seemed trivial.

It wasn't.

The interaction between minimize-to-tray, auto-lock policies, browser extension behavior, and focus management created bugs I'm still untangling.


Where OxidVault Stands Today

OxidVault v2.2.0 currently runs on Windows and includes:

  • Multi-user vaults (per-user password + TOTP)
  • ISO 27001 audit logs with SHA-256 hash chaining
  • GDPR compliance PDF export
  • SSH Quick Connect with host key verification
  • Browser extension via Native Messaging
  • Configurable auto-lock timer in the UI

Community Edition is free for up to 5 users (AGPLv3).

GitHub
πŸ‘‰ https://github.com/caRl0oo/oxidvault

Website
πŸ‘‰ https://oxidvault.com


What's Next

Windows toast notifications for expiring passwords β€” the underlying logic already exists.

And more importantly:

Actually talking to users.

That’s something I should have done much earlier.

If you have thoughts on the crypto architecture or the DEK-wrapping approach, I'd genuinely love to hear them.

Top comments (0)