DEV Community

Cover image for ๐Ÿ” Build a Zero-Knowledge DataVault in 5 Minutes (No Passwords, No Server-Side Secrets)
Ben Falik
Ben Falik

Posted on

๐Ÿ” Build a Zero-Knowledge DataVault in 5 Minutes (No Passwords, No Server-Side Secrets)

In the age of AI-driven phishing, voice cloning, and automated social engineering, traditional authentication is fundamentally broken. Passwords are leaked, SMS 2FA is intercepted, and even biometrics can be spoofed.

But there is one thing an AI, no matter how powerful, can never steal: a secret that has never touched the internet.

Enter CryptoLogin.

In this tutorial, we will build a Zero-Knowledge DataVault from scratch. Your master_secret (e.g., a 32+ character phrase based on family nicknames, kept on a piece of paper in your wallet) will never leave your device. The server will never see it, store it, or even know what it is.

Let's build it.

๐Ÿ—๏ธ The Zero-Knowledge Architecture

Unlike traditional systems that send a password (or its hash) to the server

for verification, CryptoLogin reverses the logic. The server holds no secrets.

It only issues mathematical challenges that only the holder of the

master_secret can solve.

Here is the 3-step authentication flow:

  1. Local Derivation: Your device derives a unique user_id from your master_secret using PBKDF2-HMAC-SHA512 (100,000 iterations). The secret never leaves your machine.

  2. Server Challenge: Your device sends the user_id to the server. The server generates a random cryptographic challenge (nonce) and sends it back.

  3. Local HMAC Signing: Your device signs the challenge locally using HMAC-SHA256 and the derived key. It sends only the signature to the server. The server verifies it in constant time.

The Result? If a hacker breaches the server tomorrow, they will only find hex-encoded user_ids and random challenges. Zero passwords. Zero emails. Zero risk.

๐Ÿš€ Step 1: Spin Up the Auth Server (2 Commands)

The CryptoLogin ecosystem is designed for frictionless developer experience. No complex configurations, no heavy dependencies.

Open your terminal and run:

# 1. Install the server package
pip install cryptologin[server]

# 2. Initialize the project (automatically creates `cryptologin_db`)
cryptologin init

# 3. Start the server (defaults to http://localhost:8000)
cryptologin run
Enter fullscreen mode Exit fullscreen mode

โœ… Thatโ€™s it. Your Zero-Knowledge authentication server is now running and ready to accept challenges.

๐Ÿ”‘ Step 2: Basic Zero-Knowledge Authentication (Node.js)

Letโ€™s create a simple client to prove the concept.

mkdir basic-auth && cd basic-auth
npm init -y
npm install cryptologin-client
Enter fullscreen mode Exit fullscreen mode

Create an index.js file:

import { createClient } from "cryptologin-client";

// Your 32+ char master secret. In production, read this from a local file or hardware key.
const MASTER_SECRET = "uncle-jean-1985-marseille-secret-32";

const client = createClient({ baseURL: "http://localhost:8000/api/v1" });

async function demo() {
  try {
    // 1. Register (Derives user_id locally via PBKDF2)
    await client.register(MASTER_SECRET, { username: "demo-user" });
    console.log("โœ… Registered locally.\n");

    // 2. Login (Solves the server's HMAC challenge locally)
    const session = await client.login(MASTER_SECRET);
    console.log(`๐ŸŽ‰ SUCCESS! Authenticated. Session: ${session.sessionId}`);
    console.log("๐Ÿšช The server NEVER saw your master_secret.");
  } catch (error) {
    console.error("โŒ Error:", error.message);
  }
}

demo();
Enter fullscreen mode Exit fullscreen mode

Run it with node index.js. You will see the local derivation and HMAC signing happen in a fraction of a second.

๐Ÿ—„๏ธ Step 3: Build the End-to-End Encrypted DataVault

Authentication is great, but what about storing sensitive data? We will now build a DataVault where data is encrypted locally before being sent to the server, using AES-256-GCM (the military-grade standard).

Screen_app_01

The Vault Server (Python)

Create vault_server.py. Notice the CORS middleware, allowing our local browser demo to communicate with it securely.

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import sqlite3, json

app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])

db = sqlite3.connect("vault.db", check_same_thread=False)
db.execute("CREATE TABLE IF NOT EXISTS vault (label TEXT PRIMARY KEY, ciphertext TEXT)")

class VaultItem(BaseModel):
    label: str
    ciphertext: dict

@app.post("/api/v1/vault/store")
async def store_item(item: VaultItem):
    db.execute("INSERT OR REPLACE INTO vault (label, ciphertext) VALUES (?, ?)",
               (item.label, json.dumps(item.ciphertext)))
    db.commit()
    return {"status": "stored"}

@app.get("/api/v1/vault/retrieve/{label}")
async def retrieve_item(label: str):
    row = db.execute("SELECT ciphertext FROM vault WHERE label = ?", (label,)).fetchone()
    if not row: raise HTTPException(404, "Item not found")
    return {"label": label, "ciphertext": json.loads(row[0])}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8001)
Enter fullscreen mode Exit fullscreen mode

Run it:

python vault_server.py
Enter fullscreen mode Exit fullscreen mode

Screen_app_02

The Local Encryption Client (Node.js)

Create client.js in the same folder:

import { createClient } from "cryptologin-client";
import { webcrypto } from "crypto";

const MASTER_SECRET = "uncle-jean-1985-marseille-secret-32";
const client = createClient({ baseURL: "http://localhost:8000/api/v1" });
const VAULT_URL = "http://localhost:8001/api/v1/vault";

// ๐Ÿ” Derive AES-256 key locally
async function deriveKey(secret) {
  const enc = new TextEncoder();
  const keyMaterial = await webcrypto.subtle.importKey("raw", enc.encode(secret), "PBKDF2", false, ["deriveKey"]);
  return webcrypto.subtle.deriveKey(
    { name: "PBKDF2", salt: enc.encode("cryptologin-datavault-salt-v1"), iterations: 100000, hash: "SHA-256" },
    keyMaterial, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]
  );
}

// ๐Ÿ”’ Encrypt locally
async function encryptData(secret, plaintext) {
  const key = await deriveKey(secret);
  const iv = webcrypto.getRandomValues(new Uint8Array(12));
  const ciphertext = await webcrypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(plaintext));
  return { iv: Buffer.from(iv).toString("base64"), data: Buffer.from(ciphertext).toString("base64") };
}

// ๐Ÿ”“ Decrypt locally
async function decryptData(secret, encrypted) {
  const key = await deriveKey(secret);
  const plaintext = await webcrypto.subtle.decrypt(
    { name: "AES-GCM", iv: Buffer.from(encrypted.iv, "base64") }, 
    key, Buffer.from(encrypted.data, "base64")
  );
  return new TextDecoder().decode(plaintext);
}

async function runVault() {
  console.log("๐Ÿ”’ [DataVault] E2E Encryption Test...\n");

  // 1. Auth (with auto-register fallback)
  let session;
  try { session = await client.login(MASTER_SECRET); } 
  catch { 
    await client.register(MASTER_SECRET, { username: "demo-user" }); 
    session = await client.login(MASTER_SECRET); 
  }
  console.log("โœ… Authenticated.\n");

  // 2. Local Encryption
  const secretMsg = "My Swiss account number is CH93 0076 2011 6238 5295 745 796 100.";
  console.log("๐Ÿ“ Original:", secretMsg);
  const encrypted = await encryptData(MASTER_SECRET, secretMsg);
  console.log("\n๐Ÿ” Encrypted payload sent to server:\n", encrypted);

  // 3. Store on blind server
  await fetch(`${VAULT_URL}/store`, {
    method: "POST", headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ label: "bank-account", ciphertext: encrypted })
  });
  console.log("\n๐Ÿ’พ Stored on server. The server only sees random noise.");

  // 4. Retrieve & Local Decryption
  const res = await fetch(`${VAULT_URL}/retrieve/bank-account`);
  const data = await res.json();
  const decrypted = await decryptData(MASTER_SECRET, data.ciphertext);

  console.log("\n๐Ÿ”“ Decrypted locally:", decrypted);
  console.log("\nโœจ PROVEN: The server NEVER saw the plaintext.");
}

runVault().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Run "node client.js". Watch the magic happen: the message is encrypted locally, sent as gibberish, and perfectly reconstructed on your machine.

Screen_app_03

๐ŸŒ Try the Visual Demo Yourself

Don't just take my word for it. I built a standalone, dependency-free HTML demo so you can see the Web Crypto API in action right in your browser.

๐Ÿ‘‰ Live Demo Page

Type a secret, encrypt a message, and watch the server blindly store the Base64 ciphertext. Then, decrypt it locally.

๐Ÿ’ก Conclusion: The Secret in Your Wallet

We live in an era where AI can clone voices, generate perfect phishing emails, and automate social engineering at an industrial scale. Passwords, SMS codes, and even biometrics are becoming attack vectors.

But there is one thing no AI can ever steal: what you keep in your pocket.

A 32-character master_secret based on family nicknames, an intimate secret phrase, kept on a piece of paper in your wallet. No cloud. No network. No possible leak.

CryptoLogin doesn't reinvent security. It rehabilitates it. It brings control back to where it should have never left: in your hands.

"Not your keys, not your crypto. Not your master_secret, not your account."

๐Ÿ”— Resources & Links

If you found this tutorial valuable, please โญ star the GitHub repository and - share it with your dev community. Let's build a safer internet, together.

Top comments (1)

Collapse
 
takouzlo profile image
Ben Falik

So, letโ€™s recap. Weโ€™ve set up a zero-knowledge authentication server in five minutes. Weโ€™ve created an end-to-end encrypted DataVault. And weโ€™ve proven that even in the event of a complete database breach, your data remains mathematically unreadable.
But beyond the technical aspects, thereโ€™s a philosophy.
In an age where AI can clone any voice, generate any phishing scam, automate any attackโ€ฆ the one thing it will never be able to steal is what you keep in your pocket.
A piece of paper. A 32-character master_secret. Your familyโ€™s nicknames. A personal phrase that only you know.
CryptoLogin isnโ€™t reinventing security. Itโ€™s restoring it. Itโ€™s bringing control back to where it should never have left: into your hands.
So, give it a go. Audit the code. Give it a star on GitHub. And join us in this Zero-Knowledge revolution.
Because in the age of AI, the most advanced technology there isโ€ฆ might simply be a piece of paper in your wallet.
Thank you for reading. See you soon for the next article.