Back to Documentation
Python Documentation

Python Documentation

Python 3.7+ integration guide

Official GuideProduction Ready

Python SDK Guide

Installation

bash
pip install requests cryptography
# or
pip3 install requests cryptography

Basic Usage

python
import requests
import hmac
import hashlib
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
import base64

API_BASE_URL = 'https://keyclaim.org/api'
API_KEY = 'kc_your_api_key_here'

# Step 1: Create a challenge
def create_challenge():
    response = requests.post(
        f'{API_BASE_URL}/challenge/create',
        json={'key': API_KEY, 'ttl': 30}
    )
    response.raise_for_status()
    return response.json()['challenge']

# Step 2: Generate response from challenge
# Option A: HMAC-based response (recommended for backend)
def generate_response_hmac(challenge, secret):
    return hmac.new(
        secret.encode('utf-8'),
        challenge.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

# Option B: RSA Encryption-based (for frontend with private key)
def decrypt_challenge(encrypted_challenge, private_key_pem):
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.hazmat.primitives import serialization
    
    # Load private key
    private_key = serialization.load_pem_private_key(
        private_key_pem.encode('utf-8'),
        password=None,
        backend=default_backend()
    )
    
    # Decrypt
    encrypted_bytes = base64.b64decode(encrypted_challenge)
    decrypted = private_key.decrypt(
        encrypted_bytes,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashlib.sha256()),
            algorithm=hashlib.sha256(),
            label=None
        )
    )
    return decrypted.decode('utf-8')

# Step 3: Validate the response
def validate_challenge(challenge, response, decrypted_challenge=None):
    payload = {
        'key': API_KEY,
        'challenge': challenge,
        'response': response
    }
    
    if decrypted_challenge:
        payload['decryptedChallenge'] = decrypted_challenge
    
    response_obj = requests.post(
        f'{API_BASE_URL}/challenge/validate',
        json=payload
    )
    response_obj.raise_for_status()
    return response_obj.json()

# Complete example (HMAC)
def main_hmac():
    try:
        # Create challenge
        challenge = create_challenge()
        print(f'Challenge: {challenge}')
        
        # Generate response
        secret = API_KEY
        response = generate_response_hmac(challenge, secret)
        print(f'Response: {response}')
        
        # Validate
        result = validate_challenge(challenge, response)
        print(f'Validation result: {result}')
        
        if result['valid']:
            print('✓ Challenge validated successfully!')
            if result.get('signature'):
                print(f'Signature: {result["signature"]}')
        else:
            print('✗ Validation failed')
    except Exception as error:
        print(f'Error: {error}')

# Complete example (RSA Encryption)
def main_rsa(private_key_pem):
    try:
        # Create encrypted challenge
        response_obj = requests.post(
            f'{API_BASE_URL}/challenge/create',
            json={'key': API_KEY, 'ttl': 30, 'encrypted': True}
        )
        response_obj.raise_for_status()
        data = response_obj.json()
        encrypted_challenge = data['challenge']
        
        print(f'Encrypted Challenge: {encrypted_challenge}')
        
        # Decrypt with private key
        decrypted_challenge = decrypt_challenge(encrypted_challenge, private_key_pem)
        print(f'Decrypted Challenge: {decrypted_challenge}')
        
        # Validate (send decrypted challenge as response)
        result = validate_challenge(
            encrypted_challenge,
            decrypted_challenge,  # Response is the decrypted value
            decrypted_challenge   # Also send as decryptedChallenge
        )
        
        print(f'Validation result: {result}')
        
        if result['valid']:
            print('✓ Challenge validated successfully!')
    except Exception as error:
        print(f'Error: {error}')

if __name__ == '__main__':
    # Use HMAC method
    main_hmac()
    
    # Or use RSA encryption method (if you have a private key)
    # with open('private_key.pem', 'r') as f:
    #     private_key = f.read()
    # main_rsa(private_key)

Complete SDK Class

python
import requests
import hmac
import hashlib
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
import base64

class KeyClaimClient:
    def __init__(self, api_key, base_url='https://keyclaim.org/api', private_key=None):
        self.api_key = api_key
        self.base_url = base_url
        self.private_key_pem = private_key
        
    def create_challenge(self, ttl=30):
        """Create a new challenge"""
        response = requests.post(
            f'{self.base_url}/challenge/create',
            headers={'Authorization': f'Bearer {self.api_key}'},
            json={'ttl': ttl}
        )
        response.raise_for_status()
        return response.json()
    
    def generate_response_hmac(self, challenge):
        """Generate HMAC-SHA256 response"""
        return hmac.new(
            self.api_key.encode('utf-8'),
            challenge.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
    
    def decrypt_challenge(self, encrypted_challenge):
        """Decrypt challenge with private key"""
        if not self.private_key_pem:
            raise ValueError('Private key not provided')
        
        private_key = serialization.load_pem_private_key(
            self.private_key_pem.encode('utf-8'),
            password=None,
            backend=default_backend()
        )
        
        encrypted_bytes = base64.b64decode(encrypted_challenge)
        decrypted = private_key.decrypt(
            encrypted_bytes,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashlib.sha256()),
                algorithm=hashlib.sha256(),
                label=None
            )
        )
        return decrypted.decode('utf-8')
    
    def validate(self, challenge, response, decrypted_challenge=None):
        """Validate a challenge-response pair"""
        payload = {
            'key': self.api_key,
            'challenge': challenge,
            'response': response
        }
        
        if decrypted_challenge:
            payload['decryptedChallenge'] = decrypted_challenge
        
        response_obj = requests.post(
            f'{self.base_url}/challenge/validate',
            json=payload
        )
        response_obj.raise_for_status()
        return response_obj.json()
    
    def validate_challenge(self, challenge, method='hmac'):
        """Convenience method to validate a challenge"""
        if method == 'hmac':
            response = self.generate_response_hmac(challenge)
            return self.validate(challenge, response)
        elif method == 'rsa' and self.private_key_pem:
            decrypted = self.decrypt_challenge(challenge)
            return self.validate(challenge, decrypted, decrypted)
        else:
            raise ValueError(f'Unknown method: {method}')

# Usage
client = KeyClaimClient('kc_your_api_key', 'https://keyclaim.org/api')

# HMAC method
result = client.create_challenge()
challenge = result['challenge']
validation = client.validate_challenge(challenge, 'hmac')
print(validation)

# RSA method (with private key)
# client_rsa = KeyClaimClient('kc_your_api_key', private_key=open('private_key.pem').read())
# If API key has a key pair assigned, challenge will be automatically encrypted
# result = client_rsa.create_challenge()
# challenge = result['challenge']
# validation = client_rsa.validate_challenge(challenge, 'rsa')
# print(validation)

Error Handling

python
import requests

def create_challenge_with_retry(max_retries=3):
    for i in range(max_retries):
        try:
            response = requests.post(
                f'{API_BASE_URL}/challenge/create',
                json={'key': API_KEY, 'ttl': 30},
                timeout=10
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 401:
                raise ValueError('Invalid API key')
            if e.response.status_code == 403:
                raise ValueError('Rate limited or security check failed')
            if i == max_retries - 1:
                raise
            time.sleep(1 * (i + 1))
        except requests.exceptions.RequestException as e:
            if i == max_retries - 1:
                raise
            time.sleep(1 * (i + 1))

RSA Key Pair Generation

python
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

def generate_rsa_key_pair(key_size=2048):
    """Generate RSA key pair"""
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=key_size,
        backend=default_backend()
    )
    
    # Get public key
    public_key = private_key.public_key()
    
    # Serialize private key
    private_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    )
    
    # Serialize public key
    public_pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
    
    return {
        'private_key': private_pem.decode('utf-8'),
        'public_key': public_pem.decode('utf-8')
    }

# Generate and save
key_pair = generate_rsa_key_pair(2048)

with open('private_key.pem', 'w') as f:
    f.write(key_pair['private_key'])

with open('public_key.pem', 'w') as f:
    f.write(key_pair['public_key'])

print('Key pair generated!')
print('Private key saved to: private_key.pem')
print('Public key saved to: public_key.pem')
print('\n⚠️  Keep your private key secure!')