Hostile Review is in Beta Launch β€” The Goal is Perfection  β€”  CONTACT

DeadLock Technical Specification

Implementation Guide for Developers

This document describes how DeadLock authentication works at the implementation level. Everything you need to add DeadLock to your own application is here — the architecture, the cryptographic design, database schema, code examples, and security considerations. No license, no SDK, no dependency. Just the pattern.

The Lookup Problem

With traditional authentication, you look up a user by their email address, then verify the password hash. With DeadLock there is no username — just a phrase. The system must find the user from the phrase alone. You cannot use bcrypt for this because bcrypt is intentionally non-deterministic (each hash includes a random salt). Two bcrypt hashes of the same input produce different outputs.

The solution is a dual-hash architecture: one deterministic hash for lookup, one slow hash for verification.

Architecture: Dual-Hash Design

Layer 1: HMAC-SHA256 — Lookup Index

A deterministic keyed hash using a server-side secret. The same input always produces the same output, enabling fast key-value lookup. The server secret means that even with full database access, an attacker cannot compute the HMAC without the key.

Layer 2: bcrypt — Verification Hash

A slow, salted hash stored on the user record. After HMAC lookup finds the user, bcrypt verifies the phrase is correct. 12 rounds of key stretching makes GPU-accelerated attacks impractical even if both the database and server secret are compromised.

Both hashes must pass for authentication to succeed. Compromising the database alone gives the attacker HMAC hashes they cannot reverse (no key) and bcrypt hashes that would take millennia to brute-force. Compromising the server secret alone gives them nothing without the database. Both layers must fall simultaneously.

Step 1: Normalization

Before hashing, the phrase must be normalized so that trivial variations (capitalization, extra spaces) do not prevent login. The normalization is simple and must be applied identically at set time and login time:

# Python
import re

def normalize_deadlock(phrase: str) -> str:
    """Lowercase, strip whitespace, collapse multiple spaces to single."""
    return re.sub(r'\s+', ' ', phrase.strip().lower())
// JavaScript
function normalizeDeadlock(phrase) {
    return phrase.trim().toLowerCase().replace(/\s+/g, ' ');
}

This means "The Coffee Shop", "the coffee shop", and "the  coffee  shop" all normalize to the coffee shop. The user only needs to remember the words.

Emoji in Passphrases

DeadLock phrases support full Unicode emoji. A phrase like my 🐢 loves πŸ• every friday night is valid and significantly harder to brute-force than ASCII alone. Emoji are preserved through normalization — lowercase and whitespace collapsing do not affect them.

Two rules keep emoji phrases practical:

Minimum 3 real words

A phrase must contain at least 3 words of 2+ alphabetic characters. This prevents all-emoji phrases like "πŸΆπŸ•πŸŽΈπŸ " which would be impossible to remember reliably and vulnerable to emoji dictionary attacks.

Word gap between emoji

At least one word must appear between consecutive emoji. This forces emoji to augment natural language rather than cluster together. my 🐢 loves πŸ• is valid. my πŸΆπŸ• loves dinner is not.

# Python β€” Emoji validation
import re

EMOJI_PATTERN = re.compile(
    '[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF\U0001F680-\U0001F6FF'
    '\U0001F900-\U0001F9FF\U0001FA00-\U0001FA6F\U0001FA70-\U0001FAFF'
    '\U00002702-\U000027B0\U00002600-\U000026FF\U00002764\U00002B50]'
)

def validate_emoji_rules(phrase: str) -> str | None:
    """Returns error message or None if valid."""
    words = re.findall(r'[a-zA-Z]{2,}', phrase)
    if len(words) < 3:
        return "Phrase needs at least 3 real words"

    # Check for consecutive emoji without a word between them
    tokens = re.split(r'\s+', phrase.strip())
    last_was_emoji = False
    for token in tokens:
        has_emoji = bool(EMOJI_PATTERN.search(token))
        has_word = bool(re.search(r'[a-zA-Z]{2,}', token))
        if has_emoji and not has_word:
            if last_was_emoji:
                return "Need at least one word between emoji"
            last_was_emoji = True
        else:
            last_was_emoji = False
    return None

Adding even two emoji to a phrase explodes the search space. With ~3,600 common emoji characters mixed into natural language, an attacker must now brute-force a combined ASCII + Unicode character set — effectively multiplying the keyspace by orders of magnitude per emoji position.

Step 2: HMAC-SHA256 Lookup Index

Generate a deterministic key from the normalized phrase using HMAC-SHA256 with your server secret. This key is used as a database index to find the user.

# Python
import hmac, hashlib

def deadlock_hmac(normalized: str, server_secret: str) -> str:
    """Generate deterministic lookup key from normalized phrase."""
    return hmac.new(
        server_secret.encode(),
        normalized.encode(),
        hashlib.sha256
    ).hexdigest()
// Node.js
const crypto = require('crypto');

function deadlockHmac(normalized, serverSecret) {
    return crypto
        .createHmac('sha256', serverSecret)
        .update(normalized)
        .digest('hex');
}

The server secret must be stored securely outside the database — in an environment variable, a secrets manager, or a file on disk that the application reads at startup. If it lives in the database, it defeats the purpose.

Step 3: AI Strength Review (Learning Blocklist)

Before a phrase is hashed and stored, it passes through an AI evaluator that checks for guessability. The AI reviews each phrase for verbatim famous quotes, song lyrics, movie lines, sports slogans, proverbs, and other well-known phrases that an attacker could dictionary-attack. The AI evaluates only — it never suggests alternatives. The phrase must come from the user's own memory, not from AI.

Learning blocklist

Every rejected phrase is added to a persistent blocklist keyed by its HMAC hash (the plaintext phrase is never stored). Future attempts with the same phrase are rejected instantly from the blocklist with zero API overhead. The blocklist grows smarter over time as more weak phrases are attempted.

Fail-open design

If the AI service is unavailable (network error, timeout, missing API key), the phrase is allowed through. Security should never lock users out of setting a passphrase due to a third-party dependency failure. The dual-hash architecture and minimum length requirement are the primary defenses — the AI review is an additional layer.

# Python β€” AI strength check with learning blocklist
import json, httpx

def check_phrase_strength(normalized: str, hmac_key: str, db, api_key: str) -> dict:
    """Returns {"ok": True} or {"ok": False, "reason": "..."}"""

    # Check blocklist first (instant, no API call)
    blocked = db.get(f"deadlock_blocklist:{hmac_key}")
    if blocked:
        return {"ok": False, "reason": blocked["reason"]}

    # Call AI evaluator
    try:
        resp = httpx.post(
            "https://api.anthropic.com/v1/messages",
            headers={
                "x-api-key": api_key,
                "anthropic-version": "2023-06-01",
                "content-type": "application/json",
            },
            json={
                "model": "claude-haiku-4-5-20251001",
                "max_tokens": 150,
                "messages": [{"role": "user", "content": f"Evaluate: {normalized}"}],
                "system": STRENGTH_PROMPT,
            },
            timeout=15.0,
        )
        result = resp.json()["content"][0]["text"].strip()
    except Exception:
        return {"ok": True}  # Fail open

    if result.upper().startswith("PASS"):
        return {"ok": True}

    # Extract reason and add to blocklist
    reason = result.split(":", 1)[1].strip() if ":" in result else "Too guessable"
    db.set(f"deadlock_blocklist:{hmac_key}", {
        "reason": reason,
        "added_at": datetime.utcnow().isoformat()
    })
    return {"ok": False, "reason": reason}

The AI prompt instructs the evaluator to default to PASS and only reject phrases it is 100% certain are verbatim well-known phrases. Personal phrases about pets, family, places, dates, hobbies, and memories are always approved regardless of structure. The evaluator must never suggest alternative phrases — DeadLock's security model depends on the phrase coming from human memory, not AI generation.

Step 4: bcrypt Verification Hash

Store a bcrypt hash of the normalized phrase on the user record. This is your second layer — even if an attacker obtains the HMAC key and reverses the lookup index, they still face bcrypt.

# Python β€” Setting a DeadLock phrase
import bcrypt, hashlib

def prehash(password: str) -> bytes:
    """SHA-256 prehash for phrases exceeding bcrypt's 72-byte limit."""
    raw = password.encode()
    if len(raw) <= 72:
        return raw
    return hashlib.sha256(raw).hexdigest().encode()

def set_deadlock(user_id, phrase, server_secret, db, api_key=None):
    normalized = normalize_deadlock(phrase)

    # Enforce minimum length
    if len(normalized) < 20:
        raise ValueError("Phrase must be at least 20 characters")

    # Validate emoji rules
    emoji_err = validate_emoji_rules(normalized)
    if emoji_err:
        raise ValueError(emoji_err)

    # Generate lookup key
    hmac_key = deadlock_hmac(normalized, server_secret)

    # Check for collision (do NOT reveal this to the user)
    if db.lookup_exists(f"deadlock:{hmac_key}"):
        raise ValueError("Could not set phrase. Please try a different one.")

    # AI strength review + learning blocklist
    if api_key:
        result = check_phrase_strength(normalized, hmac_key, db, api_key)
        if not result["ok"]:
            raise ValueError(result["reason"])

    # Hash for verification (prehash handles >72 byte phrases)
    phrase_hash = bcrypt.hashpw(
        prehash(normalized),
        bcrypt.gensalt(rounds=12)
    ).decode()

    # Store both
    db.set_user_field(user_id, "deadlock_hash", phrase_hash)
    db.set_user_field(user_id, "deadlock_hmac_key", hmac_key)
    db.set_lookup(f"deadlock:{hmac_key}", user_id)

Step 5: Login Flow

The login flow is: normalize → HMAC lookup → bcrypt verify → create session. No 2FA step. The phrase is the entire authentication.

# Python β€” DeadLock login
def deadlock_login(phrase, server_secret, db):
    normalized = normalize_deadlock(phrase)

    if len(normalized) < 20:
        return None  # Reject silently

    # Step 1: HMAC lookup
    hmac_key = deadlock_hmac(normalized, server_secret)
    user_id = db.get_lookup(f"deadlock:{hmac_key}")

    if not user_id:
        return None  # No user found β€” generic error

    # Step 2: bcrypt verify
    user = db.get_user(user_id)
    stored_hash = user.get("deadlock_hash")

    if not stored_hash:
        return None

    if not bcrypt.checkpw(prehash(normalized), stored_hash.encode()):
        return None  # Wrong phrase β€” same generic error

    # Step 3: Create session (skip 2FA entirely)
    session_token = db.create_session(user_id)
    return session_token

Every failure path returns the same generic error. Never indicate whether the phrase exists, whether the user exists, or what specifically failed. The attacker learns nothing from a failed attempt.

Database Schema

You need two storage structures: a field on the user record for the bcrypt hash, and a separate lookup index mapping HMAC keys to user IDs.

-- SQL (PostgreSQL / MySQL)

-- Add to your users table
ALTER TABLE users ADD COLUMN deadlock_hash VARCHAR(72) DEFAULT NULL;
ALTER TABLE users ADD COLUMN deadlock_hmac_key VARCHAR(64) DEFAULT NULL;

-- Lookup index table
CREATE TABLE deadlock_index (
    hmac_key    VARCHAR(64) PRIMARY KEY,
    user_id     INTEGER NOT NULL REFERENCES users(id)
);

-- Unique constraint prevents collision at the DB level
CREATE UNIQUE INDEX idx_deadlock_hmac ON deadlock_index(hmac_key);
# Key-Value Store (Redis, DynamoDB, etc.)

# Lookup index
deadlock:{hmac_sha256_hex} → { "user_id": 123 }

# User record fields
user:{user_id}.deadlock_hash     → "$2b$12$..."
user:{user_id}.deadlock_hmac_key → "a1b2c3..."

Removing a DeadLock Phrase

When a user removes or changes their phrase, you must delete the old HMAC lookup index and clear the hash from the user record. If changing, delete the old index before creating the new one.

# Python β€” Remove DeadLock
def remove_deadlock(user_id, db):
    user = db.get_user(user_id)
    old_key = user.get("deadlock_hmac_key")

    # Delete lookup index
    if old_key:
        db.delete_lookup(f"deadlock:{old_key}")

    # Clear user fields
    db.set_user_field(user_id, "deadlock_hash", None)
    db.set_user_field(user_id, "deadlock_hmac_key", None)

Security Considerations

Server secret management

The HMAC server secret is the crown jewel. Store it in a secrets manager, a Docker secret, or an environment variable — never in the database and never in source control. If the secret is rotated, all existing HMAC lookup indexes become invalid. Plan for this.

Rate limiting

Even though brute force is mathematically futile, rate limit login attempts anyway. It prevents resource exhaustion attacks and adds defense in depth. We use 10 attempts per 5 minutes per IP.

Generic error messages

Every failure — wrong phrase, no user found, account disabled — must return the same generic error. Never confirm or deny whether a phrase exists in the system. Never return a different HTTP status code for "phrase not found" vs "phrase wrong." Same response, every time.

Collision handling

If two users attempt to set the same phrase, the second attempt must fail with a generic error. Never say "this phrase is already in use" — that confirms the phrase exists and is a security leak. Use a message like "Could not set phrase. Please try a different one."

Minimum length

Enforce a minimum of 20 characters on both client and server. This ensures a baseline entropy floor even for the laziest users. Reject short phrases before they reach the hash functions.

bcrypt 72-byte limit & SHA-256 prehash

bcrypt truncates input at 72 bytes. For ASCII text, that is 72 characters. For UTF-8 with emoji, each emoji can be 4 bytes — an emoji-heavy phrase can exceed 72 bytes quickly. We solve this with a SHA-256 prehash: if the encoded phrase exceeds 72 bytes, hash it with SHA-256 first, then pass the hex digest (64 bytes, always under 72) to bcrypt. The HMAC layer always uses the full phrase regardless. Apply the same prehash at both set time and login time.

No CAPTCHA required

The search space of a DeadLock phrase makes automated brute force impossible. You do not need CAPTCHA for bot protection on the login form. This means no Google reCAPTCHA, no data harvested from your users during authentication, and no third-party dependency on your login path.

Complete Flow

Setting a DeadLock phrase:

  User input: "My 🐢 Loves πŸ•  Every  Friday Night"
       ↓
  Normalize:  "my 🐢 loves πŸ• every friday night"
       ↓
  Validate:   emoji rules (3+ words, word gap between emoji) → OK
       ↓
  HMAC-SHA256(normalized, server_secret) → hmac_key
       ↓
  Check: does hmac_key already exist in lookup index?
       ↓ No
  Check blocklist: deadlock_blocklist[hmac_key] exists?
       ↓ No (not previously rejected)
  AI strength review → PASS
       ↓
  Prehash if >72 bytes: SHA-256(normalized) → prehashed
  bcrypt(prehashed, rounds=12) → hash
       ↓
  Store: user.deadlock_hash = hash
         user.deadlock_hmac_key = hmac_key
         deadlock_index[hmac_key] = user_id


Setting a weak phrase (rejected):

  User input: "to be or not to be that is the question"
       ↓
  Normalize → HMAC → No collision
       ↓
  AI strength review → FAIL: This is a famous Shakespeare quote
       ↓
  Add HMAC to blocklist with reason (plaintext never stored)
       ↓
  Return rejection to user
  (Next attempt with same phrase: instant reject from blocklist, no API call)


Login with DeadLock:

  User input: "my 🐢 loves πŸ• every friday night"
       ↓
  Normalize:  "my 🐢 loves πŸ• every friday night"
       ↓
  HMAC-SHA256(normalized, server_secret) → hmac_key
       ↓
  Lookup: deadlock_index[hmac_key] → user_id
       ↓ Found
  Prehash if >72 bytes: SHA-256(normalized) → prehashed
  bcrypt.verify(prehashed, user.deadlock_hash) → True
       ↓
  Create session → return token
  (skip 2FA entirely)

Entropy Analysis

For a phrase of length L drawn from a character set of size C:

Classical entropy:  bits = L × log2(C)
Quantum (Grover):  effective_bits = bits / 2

Example: "the parking lot behind the library floods" (42 chars)
Conservative charset: 95 printable ASCII

  Classical:  42 × log2(95) = 42 × 6.57 = ~276 bits
  Quantum:    276 / 2 = ~138 bits

  At 10^12 ops/sec (theoretical quantum computer):
  2^138 / 10^12 = ~3.5 × 10^29 seconds = ~11 sextillion years

With Unicode/emoji (charset ~150,000):

  Classical:  42 × log2(150000) = 42 × 17.19 = ~722 bits
  This is beyond meaningfully quantifiable.
Implementation Notes
1. DeadLock was created by Hostile Review. This specification is public and free to implement.
2. The code examples above are pseudocode-level. Adapt to your language, framework, and database.
3. The normalization function must be identical on set and login. Test edge cases: leading/trailing spaces, tabs, multiple spaces, mixed case, unicode whitespace.
4. Do not add CAPTCHA to the DeadLock login form. It is unnecessary and compromises user privacy.
5. The AI strength review is optional but recommended. It prevents common phrases from entering the system and builds a self-improving blocklist over time. Use any LLM with a strict evaluator prompt.
6. Emoji support is optional. If you implement it, apply the same prehash at both set and login time, and enforce word-gap rules to prevent all-emoji phrases.
7. Credits in code to Hostile Review & Apollo Raines always appreciated!
We invented DeadLock, but it’s free to the world.
Quantum computing is upon us, and we need simpler security that can beat it.
Created by Hostile Review — hostilereview.com
HostileReview is powered by our CodeForge Engine Ask AI About Us
S
Sharona-AI
Online