___ ___
/ \ / \ VESvault
\__ / \ __/ Encrypt Everything without fear of losing the Key
\\ // https://vesvault.com https://ves.host
\\ //
___ \\_//
/ \ / \ VESlocker: Hardware-grade PIN Security API
\__ / \ __/
\\ // https://veslocker.com
\\ //
\\_//
/ \
\___/
Give a short PIN the strength of a hardware security module — without the hardware.
A 4-digit PIN has only 10,000 combinations. Anywhere it guards data that an attacker can copy (a browser, a file, a backup), they can brute-force it offline in milliseconds. VESlocker removes the offline attack entirely: the decryption key is split between the user's PIN and a secret held by a small key server, and the server hands the key back only under a strict, exponentially-throttled budget. After a few dozen wrong guesses the entry is locked for good — a true lifetime cap on attempts, the way a TPM or a secure enclave behaves, implemented in ~250 lines of vanilla JS and ~110 lines of PHP.
🔎 Try it: there is a live demo at veslocker.com.
client key server
────── ──────────
challenge = SHA-256(seed ‖ PIN) ───── id, challenge ─────▶ look up per-id secret
throttle check (2^attempts)
key = AES-GCM key ◀──── SHA-256(secret ‖ challenge) ────── return derived key
ciphertext = AES-GCM(key, data)
(ciphertext + seed are stored locally; the key is never stored)
- The data is encrypted client-side with AES-GCM. The key is derived from
the server's per-entry
secretand achallengeseeded by the user's PIN. - The server never receives the seed, the PIN, the plaintext, or the
ciphertext — only an
idand achallenge(a hash). It returns the derived key, gated by throttling. - Each request to an
idincrements an attempt counter; the next request is refused until2^attemptsseconds have passed (capped at 32). After ~32 attempts the wait exceeds a century — the entry is effectively sealed.
<script src="client/VESlocker.js"></script>
<script>
const vl = new VESlocker({
apiUrl: "https://your-key-server.example/VESlocker.php"
});
// Encrypt a secret behind a PIN and stash it in localStorage under a name:
await vl.store("launch-codes", "1234", "the actual launch codes");
// Later — read it back with the PIN:
const secret = await vl.peek("launch-codes", "1234"); // "the actual launch codes"
// Re-encrypt under a new PIN (and refresh the stored token):
await vl.fetch("launch-codes", "1234", "5678");
</script>Prefer to manage storage yourself? encrypt / decrypt are stateless and
return / take a self-contained token:
const token = await vl.encrypt("1234", "the actual launch codes");
const back = await vl.decrypt("1234", token); // "the actual launch codes"Get notified when an entry's key is accessed (an early-warning signal that someone is guessing the PIN):
vl.accessFn = (id, count, at) => {
if (count > 0) console.warn(`VESlocker '${id}' accessed ${count}× (last: ${at})`);
};- Serve
server/VESlocker.phpbehind any PHP-capable HTTP server. - Create the database table from
server/VESlocker.sql. - Set the
$DB*connection settings at the top ofVESlocker.php. - Point the client's
apiUrlat it.
Requirements — Server: PHP + MySQL/MariaDB. Client: any browser with Web Crypto (all current browsers).
VESlocker is a key oracle gated by throttling, not a vault. Reason about it honestly before you deploy:
- ✅ The server cannot read your data. It never sees plaintext or ciphertext — only an id and a challenge hash.
- ✅ No offline brute force. An attacker who copies the ciphertext cannot test PINs offline: the key also requires the server's secret, which they don't have.
- ✅ Hard lifetime cap on online guesses. Exponential per-id throttling limits an attacker (and the legitimate user) to roughly 32 attempts, ever.
⚠️ Availability dependency. Decryption requires the key server to be reachable. This is by design — it is what makes the throttle unavoidable.⚠️ Two-secret compromise. If an attacker obtains both the server-side secret store and the ciphertext, the PIN can be brute-forced offline (bounded only by PIN entropy). Protect the key server's database accordingly, and treat the PIN's entropy as your last line of defense in that scenario.
Found a vulnerability? See SECURITY.md — please report privately.
VESlocker is licensed per component:
| Component | License | |
|---|---|---|
client/ |
Apache License 2.0 | Embed it in your app with no copyleft obligations. |
server/ |
PolyForm Perimeter 1.0.1 | Use, modify, and self-host for any purpose — except offering a product that competes with VESlocker. |
Contributions are welcome under the DCO — see
CONTRIBUTING.md. In short: sign off your commits with
git commit -s.
Part of the VESvault project — Encrypt Everything without fear of losing the Key.