ALTCHA Library
The `altcha` library provides tools for creating and verifying ALTCHA challenges, a privacy-friendly, self-hosted, and free alternative to CAPTCHA. It allows Python applications to generate cryptographic proof-of-work challenges and validate responses from clients, protecting against bots and spam. The current version is 2.0.0, and it follows an infrequent but impactful release cadence, with major versions introducing API changes.
Common errors
-
AttributeError: module 'altcha' has no attribute 'verify_challenge'
cause You are attempting to use the old v1.x top-level `verify_challenge` function in altcha v2.x.fixInitialize the `Altcha` class and call its `verify` method: `altcha_instance = Altcha(secret_key); result = altcha_instance.verify(client_response_data)`. -
ValueError: Secret key must be a string of at least 32 characters.
cause The `secret_key` provided when initializing `Altcha` is too short or invalid.fixProvide a strong, random secret key that is at least 32 characters long. Example: `Altcha('your-strong-random-secret-key-of-32-chars')`. -
Challenge verification failed: challenge_expired
cause The `expire` timestamp included in the client's response data indicates that the challenge is no longer valid.fixEnsure challenges are generated with a realistic expiry (e.g., a few minutes into the future) and client responses are processed within that time frame. Check system clocks for synchronization issues. -
Challenge verification failed: invalid_signature
cause The signature provided by the client does not match the signature computed by the server using its `secret_key` or the challenge data has been tampered with.fixVerify that the `secret_key` used to initialize `Altcha` on the server is identical to the one used for challenge generation. Ensure the challenge data (`challenge`, `salt`, `expire`) sent to the client and received back has not been altered.
Warnings
- breaking Version 2.0.0 introduces significant breaking changes compared to 1.x. The primary API now revolves around the `Altcha` class, which must be initialized with a secret key. Top-level functions like `generate_challenge` and `verify_challenge` are removed; their functionality is now available as methods on an `Altcha` instance.
- gotcha The `secret_key` used for initializing the `Altcha` object is critical. It must be a strong, random string (at least 32 characters long) and kept consistent between challenge generation and verification. Any mismatch or compromise of this key will lead to verification failures or security vulnerabilities.
- gotcha This Python library handles only the server-side logic (challenge generation and verification). A separate client-side JavaScript library (or custom client implementation) is required to solve the challenges and provide the 'response' string that this library then verifies.
- gotcha Challenges have an `expire` timestamp. If a client's response is received and verified after this timestamp, the verification will fail with a `challenge_expired` error.
Install
-
pip install altcha
Imports
- Altcha
from altcha.challenge import Altcha
from altcha import Altcha
- ChallengeResult
from altcha import ChallengeResult
Quickstart
import os
import time
from altcha import Altcha, ChallengeResult
# Initialize Altcha with your secret key
# IMPORTANT: Replace 'altcha-dev-secret-key...' with a strong, random, 32+ character key
# and set it via an environment variable in production (e.g., ALTCHA_SECRET_KEY).
secret_key = os.environ.get('ALTCHA_SECRET_KEY', 'altcha-dev-secret-key-1234567890abcdef')
if secret_key == 'altcha-dev-secret-key-1234567890abcdef':
print("WARNING: Using a default development secret key. Set ALTCHA_SECRET_KEY environment variable with a strong, random key in production!")
altcha = Altcha(secret_key)
# --- QUICKSTART PART 1: Generate a Challenge ---
print("\n--- Generating a Challenge ---")
# The 'challenge_obj' contains all necessary fields for the client.
challenge_obj = altcha.generate_challenge()
challenge_for_client = challenge_obj.to_json() # This JSON string is what you send to the client.
print(f"Generated Challenge (for client): {challenge_for_client}")
# In a real scenario, the client-side JavaScript library would solve this challenge
# and send back the original challenge data along with a computed 'response' string.
# --- QUICKSTART PART 2: Verify a Challenge Response ---
print("\n--- Verifying a Challenge Response ---")
# For demonstration, we'll use a hardcoded valid challenge/response pair.
# This 'solved_client_response' simulates what a client would send back after solving.
# The 'response' field comes from the client-side JS library's computation.
solved_client_response = {
"challenge": "b4e4d7730e6a8e8073b64c748c5a21e421e421e4",
"signature": "283738b52a16d8a39e99279a059c259687e35b7501a4e1d1f042657d47833072",
"algorithm": "sha1",
"salt": "altcha_salt",
"expire": int(time.time() + 3600), # Ensure expiry is in the future for verification.
"response": "sha1:1000:altcha_salt:s/h0QhQ2L2yYmYg5X2V5Q5R5R5Q5Y5h5y5Q5L2xY="
}
try:
verification_result: ChallengeResult = altcha.verify(solved_client_response)
if verification_result.verified:
print("✅ Challenge Verified Successfully!")
else:
print(f"❌ Challenge Verification Failed: {verification_result.error}")
# Common errors: 'challenge_expired', 'invalid_signature', 'incorrect_proof'
except ValueError as e:
print(f"❌ Verification Error (ValueError): {e}")
# --- QUICKSTART PART 3: Demonstrate an Invalid Response ---
print("\n--- Demonstrating an Invalid Response ---")
# An example where the challenge hash is intentionally wrong.
invalid_response_data = {
"challenge": "wrong_challenge_hash",
"signature": solved_client_response["signature"],
"algorithm": solved_client_response["algorithm"],
"salt": solved_client_response["salt"],
"expire": solved_client_response["expire"],
"response": solved_client_response["response"]
}
try:
invalid_result = altcha.verify(invalid_response_data)
if not invalid_result.verified:
print(f"✅ Invalid challenge handled correctly. Error: {invalid_result.error}")
else:
print("❌ Unexpected: Invalid challenge was verified.")
except ValueError as e:
print(f"✅ Invalid challenge handled correctly with ValueError: {e}")