Introduction
The goal of these exercises is to explore the functionalities of Zero Knowledge Proofs in the scope of a mock authentication process.
Setup
This lab requires that you obtain and install noknow
python package available at: https://github.com/jpbarraca/noknow-python
It will include both the noknow
system, developed by https://github.com/GoodiesHQ and a client and server developed for this lab.
Obtain the repository, install the requirements and install the package, with the following commands:
sudo apt install python3-virtualenv
git clone https://github.com/jpbarraca/noknow-python.git
cd noknow-python
virtualenv venv
bash
source venv/bin/activate
pip install -r requirements
python3 setup.py install
cd examples
Zero Knowledge Proofs
zero-knowledge proof or zero-knowledge protocol is a method by which one party (the prover) can prove to another party (the verifier) that a given statement is true, while avoiding conveying to the verifier any information beyond the mere fact of the statement’s truth. The intuition underlying zero-knowledge proofs is that it is trivial to prove the possession of certain information by simply revealing it
- the challenge is to prove this possession without revealing the information, or any aspect of it whatsoever.
In light of the fact that one should be able to generate a proof of some statement only when in possession of certain secret information connected to the statement, the verifier, even after having become convinced of the statement’s truth, should nonetheless remain unable to prove the statement to third parties. 1
An important protocol in the scope of identities is the Schnorr’s identification protocol, which consists of a public-key based challenge-response identification protocol. In the scope of ZKP, the protocol can operate both in interactive and non-interactive modes, making it very versatile.
The Verifier
In our simplified scenario we will use a Flask webserver that acts as a verifier. Therefore, it will validate the identity of
provers (users) that wish to access the resources.
For practical purposes, we will also support a register
endpoint which allows users to register their identity.
This code is available in the server.py
file below. It is a simple Flask webserver with the following properties:
- Supports
register
andlogin
methods. - The
register
method allows clients to register their keys, and corresponds to an initial provisioning. - During the
register
phase, the client will set a password which can be later used for authentication. While the password is used in this phase, the key is not disclosed to the server. - The
login
method has two actions: sending a token, which acts as a challenge. - The client can take the token and transform it using the password. It proves to know the password without disclosing it.
- The server also signs their tokens to prevent forgery, and the signing key is unique per client
from flask import Flask
from flask import request
from noknow.core import ZK, ZKSignature, ZKParameters, ZKData, ZKProof
import os
import json
app = Flask(__name__)
clients = {}
@app.route("/")
def index():
return "Howdy"
@app.route("/register", methods=["POST"])
def register():
clientid = request.json.get('clientid', None)
sig = request.json.get('sig', None)
if sig is None or clientid is None:
return "Invalid request"
if clientid in clients:
return "Client already registered"
client_signature = ZKSignature.from_json(sig)
client_zk = ZK(client_signature.params)
clients[clientid] = {'zk': client_zk, 'sig': client_signature}
return "Client registered"
@app.route("/login", methods=["POST"])
def login():
clientid = request.json.get('clientid', None)
proof = request.json.get('proof', None)
# First request, send token to client for proof
if clientid in clients and proof is None:
# Generate a server password and ZK object for the server
server_password = os.urandom(32)
# Set up server component, generating a new ZK object
server_zk = ZK.new(curve_name="secp384r1", hash_alg="sha3_512")
# store the server zk and password for later use
clients[clientid]['server_zk'] = server_zk
clients[clientid]['server_password'] = server_password
# get the client zk
client_zk = clients[clientid]['zk']
# Create a signed token and send to the client
token = server_zk.sign(server_password, client_zk.token())
return token.to_json()
if clientid in clients and proof is not None:
# Get the token from the client
zkproof = ZKData.from_json(proof)
token = ZKData.from_json(zkproof.data)
# Get the client zk and server password
server_zk = clients[clientid]['server_zk']
server_signature = server_zk.create_signature(clients[clientid]['server_password'])
client_zk = clients[clientid]['zk']
# check if the server signature is valid
if not server_zk.verify(token, server_signature):
print("Invalid server auth: ")
return "Authentication failure"
else:
# Verify the proof from the client
# uses the client proof and signature to verify the token
result = client_zk.verify(zkproof, clients[clientid]['sig'], data=token)
return "Authentication success" if result else "Authentication failure"
# Invalid request
return "Invalid request"
if __name__ == "__main__":
app.run(debug=True)
The Prover
The prover corresponds to the client, and will execute two actions: register a key and then create a proof that asserts the identity. The proof is only valid for a single client, at a single server. The proof doesn’t include the key but the creation of the proof requires knowing the key. The server can verify that the proof is correct even without knowing the key.
The code for this client is present in the client.py
directory and below.
The client first registers itself into the server, storing its signature in the server. The signature depends on the password
and on a private key which is kept.
Then the client will contact the server and request a token
. This token can be used to build a proof
together with the password
, and is sent to the server, which replies with the result.
The client will ask for a password
twice, and you can provide a different password
to see what happens (it will fail to prove the identity as expected).
from getpass import getpass
import requests
from noknow.core import ZK, ZKSignature, ZKParameters, ZKData, ZKProof
import uuid
print("Registering client")
client_zk = ZK.new(curve_name="secp256k1", hash_alg="sha3_256")
clientid = uuid.uuid4().hex
password = getpass("Enter Password: ")
# Create signature and send to server
signature = client_zk.create_signature(password)
print("Client: signature ", signature.to_json())
r = requests.post("http://localhost:5000/register", json={"clientid": clientid, "sig": signature.to_json()})
print(r.status_code)
input("Press Enter to continue...")
print("Logging in")
password = getpass("Enter Password: ")
print("Getting token...")
r = requests.post("http://localhost:5000/login", json={"clientid": clientid})
print("Token: ", r.json())
token = r.text
proof = client_zk.sign(password, token).to_json()
print("Proof: ", proof)
print("Sending proof...")
r = requests.post("http://localhost:5000/login", json={"clientid": clientid, "proof": proof})
print(r.text)
TASKS:
- Obtain the code install the dependencies
- Execute the server in one terminal and the client on another terminal
- Provide the same, or different passwords
- Analyze the messages exchanged
References
- https://github.com/jpbarraca/noknow-python
- Ioannis Chatzigiannakis, Apostolos Pyrgelis, Paul G. Spirakis, Yannis C. Stamatiou, Elliptic Curve Based Zero Knowledge Proofs and Their Applicability on Resource Constrained Devices, https://arxiv.org/pdf/1107.1626