Most people don't think much about share links. You drop a file in a chat, paste a URL, the other side opens it. Maybe you slap a password on it because the file is sensitive. Done.
The problem: if the password ends up at a determined attacker, "is it safe?" depends on details the average product doesn't get right. We rebuilt our share-link pipeline late last quarter after auditing ourselves with the assumption an attacker has already obtained the URL. Here's what changed.
From SHA-256 to bcrypt
The first iteration of password-protected links hashed the password with SHA-256 before storing it. That sounds fine — SHA-256 is famously a cryptographic hash. But SHA-256 is the wrong tool for password storage. It's designed to be fast, and "fast" is exactly what an attacker wants when they're trying every six-character combination.
A modern GPU rig can compute billions of SHA-256 hashes per second. Against a six-character lowercase-and-digit password (~2 billion combinations), that's done in roughly one second of compute.
We migrated to bcrypt, which deliberately costs ~100 ms per hash on commodity hardware. That same brute-force run becomes a months-long project on dedicated hardware — and at that point the attacker is better off targeting something else.
Lockout, not just slow hashing
Slow hashes only matter if an attacker has to keep talking to our server to verify each guess. So we layered an account-style lockout on top of every protected link:
- 5 wrong password attempts within 15 minutes → the link is locked for the requesting IP.
- Locked-out IPs get a generic 403 (no "wrong password" feedback that confirms the link still exists).
- Failed attempts are logged with timestamp and IP for the link owner to review.
Link IDs are themselves secrets
Each share link gets a 128-bit random identifier. Knowing the link is half the credential — even an unprotected link should be unguessable. We use crypto.getRandomValues() on creation and never recycle IDs.
Expiry and view limits ship by default
When a user creates a link, they pick an expiry (default 7 days) and an optional view cap. Both are enforced server-side before the file is decrypted from storage. A link that's expired or out of views returns the same 404 a non-existent link would — no signal that it once existed.
What this costs you
As the link sender: nothing. The defaults are sensible (7-day expiry, no view cap, optional password). As a hypothetical attacker: months of dedicated compute for a single password-protected link, with the link owner seeing repeated failed-attempt notifications the whole time.
We think the win-loss math for protected share links should land squarely on the side of the rightful owner. This is how we get there.