cryptography

Implementing Zero-Knowledge Proof for Authentication

Zero-Knowledge Proofs (ZKPs) allow a prover to demonstrate knowledge of a secret (such as a password or private key) without revealing the secret itself. This skill implements the Schnorr identification protocol and a simplified ZKPP (Zero-Knowledge Password Proof) using the discrete logarithm problem, enabling authentication where the server never learns the user's password.

authenticationcryptographyprivacyzero-knowledge-proofzkp
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Zero-Knowledge Proofs (ZKPs) allow a prover to demonstrate knowledge of a secret (such as a password or private key) without revealing the secret itself. This skill implements the Schnorr identification protocol and a simplified ZKPP (Zero-Knowledge Password Proof) using the discrete logarithm problem, enabling authentication where the server never learns the user's password.

When to Use

  • When deploying or configuring implementing zero knowledge proof for authentication capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Familiarity with cryptography concepts and tools
  • Access to a test or lab environment for safe execution
  • Python 3.8+ with required dependencies installed
  • Appropriate authorization for any testing activities

Objectives

  • Implement Schnorr's identification protocol for ZKP authentication
  • Build a non-interactive ZKP using Fiat-Shamir heuristic
  • Implement zero-knowledge password proof (ZKPP)
  • Demonstrate completeness, soundness, and zero-knowledge properties
  • Compare ZKP authentication with traditional password verification

Key Concepts

ZKP Properties

Property Description
Completeness Honest prover always convinces honest verifier
Soundness Dishonest prover cannot convince verifier (except negligible probability)
Zero-Knowledge Verifier learns nothing beyond the statement's truth

Schnorr Protocol

  1. Setup: Public generator g, prime p, q (order of g)
  2. Registration: Prover computes y = g^x mod p (public key from secret x)
  3. Commitment: Prover sends t = g^r mod p (random r)
  4. Challenge: Verifier sends random c
  5. Response: Prover sends s = r + c*x mod q
  6. Verify: Check g^s == t * y^c mod p

Security Considerations

  • Use cryptographically secure random number generators
  • Challenge must be unpredictable (from verifier's perspective)
  • For non-interactive proofs, use Fiat-Shamir with collision-resistant hash
  • ZKP alone does not provide forward secrecy; combine with TLS

Validation Criteria

  • Honest prover always verifies successfully (completeness)
  • Random response without secret does not verify (soundness)
  • Server never receives the secret value
  • Non-interactive proof is verifiable offline
  • Multiple authentications produce different transcripts
  • Protocol resists replay attacks
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.6 KB

API Reference: Zero-Knowledge Proof Authentication

hashlib (Python Standard Library)

PBKDF2 Key Derivation

import hashlib
key = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), iterations)

SHA-256 Hashing (Fiat-Shamir Heuristic)

challenge = int(hashlib.sha256(data.encode()).hexdigest(), 16) % prime

secrets (Python Standard Library)

Function Description
secrets.randbelow(n) Cryptographically secure random int in [0, n)
secrets.token_hex(n) Random hex string of n bytes
secrets.token_bytes(n) Random bytes of length n

Schnorr Protocol Steps

Step Prover Verifier
Setup Private key x, public key y=g^x mod p Knows g, p, y
Commit Pick random k, send r=g^k mod p Receive r
Challenge - Send random c
Response Send s = k - c*x mod (p-1) Check g^s * y^c == r mod p

Fiat-Shamir Heuristic (Non-Interactive)

c = H(g || r || y)   # Challenge derived from hash
s = k - c * x mod (p-1)

ZKP Properties

Property Guarantee
Completeness Honest prover always convinces verifier
Soundness Dishonest prover fails with high probability
Zero-Knowledge Verifier learns nothing beyond validity

References

standards.md1.0 KB

Standards and References - Zero-Knowledge Proof for Authentication

Academic References

Schnorr Identification Protocol

  • Paper: "Efficient Signature Generation by Smart Cards" (Claus-Peter Schnorr, 1989)
  • Standard: ISO/IEC 9798-5 (Entity authentication using zero-knowledge techniques)

Fiat-Shamir Heuristic

  • Paper: "How To Prove Yourself" (Fiat, Shamir, 1986)
  • Description: Converts interactive ZKP to non-interactive using hash function

RFC 8235 - Schnorr Non-Interactive Zero-Knowledge Proof

RFC 5054 - SRP (Secure Remote Password)

Python Libraries

py-ecc

cryptography

workflows.md1.3 KB

Workflows - Zero-Knowledge Proof for Authentication

Workflow 1: Schnorr Interactive ZKP

Prover (knows secret x)              Verifier (knows y = g^x mod p)
      |                                      |
      |-- Commitment: t = g^r mod p -------->|
      |                                      |
      |<-- Challenge: c (random) ------------|
      |                                      |
      |-- Response: s = (r + c*x) mod q ---->|
      |                                      |
      |                              [Verify: g^s == t * y^c mod p]
      |                              [Accept or Reject]

Workflow 2: Non-Interactive ZKP (Fiat-Shamir)

Prover:
  1. Choose random r
  2. Compute t = g^r mod p
  3. Compute c = H(g || y || t)  (Fiat-Shamir)
  4. Compute s = (r + c*x) mod q
  5. Send proof (t, s) to verifier
 
Verifier:
  1. Compute c = H(g || y || t)
  2. Check g^s == t * y^c mod p

Workflow 3: Registration and Authentication

[Registration]:
  User --> [Choose password/secret x]
       --> [Compute y = g^x mod p]
       --> [Send y to server]
  Server --> [Store y (public key only)]
 
[Authentication]:
  User <--> Server: [Run Schnorr protocol]
  Server: [Verifies proof without learning x]
  Server: [Grants session token on success]

Scripts 1

agent.py5.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for implementing zero-knowledge proof authentication using Schnorr protocol."""

import hashlib
import secrets
import json
import argparse
from datetime import datetime


# Safe prime and generator for discrete log ZKP
SAFE_PRIME = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF
GENERATOR = 2


def generate_keypair():
    """Generate a ZKP key pair (private key x, public key y = g^x mod p)."""
    x = secrets.randbelow(SAFE_PRIME - 2) + 1
    y = pow(GENERATOR, x, SAFE_PRIME)
    print(f"[*] Generated key pair")
    print(f"  Public key (y): {hex(y)[:40]}...")
    return x, y


def schnorr_prove(private_key):
    """Generate a Schnorr ZKP proof (commitment, challenge, response)."""
    k = secrets.randbelow(SAFE_PRIME - 2) + 1
    r = pow(GENERATOR, k, SAFE_PRIME)
    # Fiat-Shamir heuristic: non-interactive challenge
    c_input = f"{GENERATOR}{r}{pow(GENERATOR, private_key, SAFE_PRIME)}"
    c = int(hashlib.sha256(c_input.encode()).hexdigest(), 16) % SAFE_PRIME
    s = (k - c * private_key) % (SAFE_PRIME - 1)
    return {"commitment": r, "challenge": c, "response": s}


def schnorr_verify(public_key, proof):
    """Verify a Schnorr ZKP proof without learning the private key."""
    r, c, s = proof["commitment"], proof["challenge"], proof["response"]
    lhs = pow(GENERATOR, s, SAFE_PRIME) * pow(public_key, c, SAFE_PRIME) % SAFE_PRIME
    valid = lhs == r
    print(f"  [{'+'if valid else '!'}] Verification: {'PASSED' if valid else 'FAILED'}")
    return valid


def zkp_password_register(password):
    """Register a password using ZKP (server stores only public key)."""
    salt = secrets.token_hex(16)
    pwd_hash = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 100000)
    x = int.from_bytes(pwd_hash, "big") % (SAFE_PRIME - 2) + 1
    y = pow(GENERATOR, x, SAFE_PRIME)
    print(f"[*] Registered user (server stores salt + public key, never the password)")
    return {"salt": salt, "public_key": y, "private_key": x}


def zkp_password_authenticate(password, registration):
    """Authenticate using ZKP (prove password knowledge without revealing it)."""
    salt = registration["salt"]
    pwd_hash = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 100000)
    x = int.from_bytes(pwd_hash, "big") % (SAFE_PRIME - 2) + 1
    proof = schnorr_prove(x)
    valid = schnorr_verify(registration["public_key"], proof)
    return valid


def run_protocol_demo(rounds=5):
    """Demonstrate ZKP authentication protocol with multiple rounds."""
    print("[*] ZKP Schnorr Protocol Demo\n")
    x, y = generate_keypair()
    successes = 0
    for i in range(rounds):
        print(f"\n[*] Round {i+1}/{rounds}")
        proof = schnorr_prove(x)
        if schnorr_verify(y, proof):
            successes += 1
    print(f"\n[*] Protocol: {successes}/{rounds} rounds passed")
    print(f"[*] Completeness: {'VERIFIED' if successes == rounds else 'FAILED'}")
    # Soundness test: wrong key should fail
    wrong_x = secrets.randbelow(SAFE_PRIME - 2) + 1
    wrong_proof = schnorr_prove(wrong_x)
    forgery = schnorr_verify(y, wrong_proof)
    print(f"[*] Soundness (wrong key rejected): {'VERIFIED' if not forgery else 'FAILED'}")
    return successes == rounds and not forgery


def run_password_demo(password="SecureP@ss123"):
    """Demonstrate ZKP password authentication."""
    print("\n[*] ZKP Password Authentication Demo\n")
    reg = zkp_password_register(password)
    print("\n[*] Authenticating with correct password...")
    ok = zkp_password_authenticate(password, reg)
    print(f"  Result: {'Authenticated' if ok else 'Rejected'}")
    print("\n[*] Authenticating with wrong password...")
    bad = zkp_password_authenticate("WrongPassword", reg)
    print(f"  Result: {'Authenticated' if bad else 'Rejected'}")
    return ok and not bad


def main():
    parser = argparse.ArgumentParser(description="Zero-Knowledge Proof Authentication Agent")
    parser.add_argument("action", choices=["demo-protocol", "demo-password", "keygen", "full-test"])
    parser.add_argument("--rounds", type=int, default=5, help="Protocol verification rounds")
    parser.add_argument("--password", default="SecureP@ss123", help="Password for ZKP demo")
    parser.add_argument("-o", "--output", default="zkp_report.json")
    args = parser.parse_args()

    report = {"date": datetime.now().isoformat(), "action": args.action}
    if args.action == "keygen":
        x, y = generate_keypair()
        report["public_key"] = hex(y)
    elif args.action == "demo-protocol":
        report["protocol_valid"] = run_protocol_demo(args.rounds)
    elif args.action == "demo-password":
        report["password_auth_valid"] = run_password_demo(args.password)
    elif args.action == "full-test":
        report["protocol_valid"] = run_protocol_demo(args.rounds)
        report["password_auth_valid"] = run_password_demo(args.password)

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"\n[*] Report saved to {args.output}")


if __name__ == "__main__":
    main()
Keep exploring