Friendly Captcha Solver
Proof-of-work CAPTCHA solver for Friendly Captcha v1 - zero dependencies.
Friendly Captcha is a privacy-focused, proof-of-work based CAPTCHA alternative popular with European (Germany/Austria/Netherlands) companies avoiding Google reCAPTCHA. This solver implements the official friendly-pow blake2b-256 puzzle algorithm from scratch, using only the Python standard library.
Table of Contents
- Features
- v1 vs v2
- Installation
- Quick Start
- Usage
- Algorithm Reference
- API Reference
- Performance
- Validation
- Limitations
- Security Considerations
- License
Features
- Zero dependencies: pure Python standard library (
hashlib.blake2b,base64,urllib) - Faithful to spec: implements the exact byte layout and difficulty formula from the official
friendly-powrepository - Cross-validated: verified against real production puzzles and a genuine solution produced by Friendly Captcha's own WASM client (see Validation)
- CLI and library: solve a puzzle string directly, or fetch a live one from a sitekey
- Local verification: check that a set of solutions actually satisfies the puzzle's difficulty and uniqueness rules
v1 vs v2
Friendly Captcha has two service versions with different protocols:
| v1 | v2 | |
|---|---|---|
| Mechanism | Pure proof-of-work puzzle | PoW plus proprietary risk/fingerprint signals |
| API | api.friendlycaptcha.com/api/v1/* |
global.frcapi.com/api/v2/* (session-based: agent_id, sess_id, signals) |
| Spec | Open, source-available | Not public |
| This tool | ✅ Fully supported | ❌ Not supported |
This solver only targets v1. v2 layers browser fingerprinting and behavioral "risk intelligence" signals on top of the puzzle. Inspecting the @friendlycaptcha/sdk source (src/signals/collect.ts) confirms it's collecting real behavioral telemetry, not just a puzzle:
- Mouse/touch movement velocity, distance and duration (rolling stats sampled every 50ms)
- Keystroke timing, categorized by key (backspace, tab, enter, arrows, etc.)
event.isTrustedon every observed event — which is impossible to forge viadispatchEvent(), since browsers always mark script-dispatched events as untrusted- Native-function tamper detection (
collectStacktrace.ts/patchNativeFunctions) — designed to notice hooked/patched browser internals, the kind of thing automation/instrumentation tooling does - Device orientation/motion (mobile), persistent session id, whether the page is framed, and a call-stack snapshot
This is a fundamentally different kind of problem than solving a documented hash puzzle: it would require driving a real browser with human-plausible mouse/keyboard input (not JS-dispatched events) and still gives no guarantee of success, since the server-side risk score can also weigh IP reputation, TLS fingerprint, and history that no client-side tool controls. That's out of scope for this project.
If a target site uses v1 (the widget script is friendly-challenge/widget.module.min.js, not @friendlycaptcha/sdk/site.min.js), this tool covers it completely.
Installation
pip install friendlycaptcha-solver
From Source
git clone https://github.com/opastorello/friendlycaptcha-solver.git
cd friendlycaptcha-solver
pip install .
Requirements: Python 3.6+
Quick Start
from friendlycaptcha_solver import FriendlyCaptchaSolver
solver = FriendlyCaptchaSolver()
# Solve a puzzle you already fetched (e.g. from a target site's network requests).
# This example puzzle was captured live for this README and is likely expired
# by now (puzzles expire ~1h after issuance) - solving/verifying it still works
# locally either way, since that's purely a client-side computation; only
# submitting an expired one to the real server would be rejected.
puzzle = "a4b457ba7e0eb129917220c6e145806a.aqhyLQdbzRWKY/UQAQwwowAAAAAAAAAAEkSoo1h4Aro="
solution = solver.solve(puzzle)
print(solution["response"]) # submit this as the "frc-captcha-solution" form field
Or fetch a live puzzle directly from a sitekey:
puzzle = solver.fetch_puzzle("FCMGEMUD2M567T8G")
solution = solver.solve(puzzle)
Usage
Python Library
from friendlycaptcha_solver import FriendlyCaptchaSolver
solver = FriendlyCaptchaSolver()
# Decode puzzle metadata without solving
info = solver.decode_puzzle(puzzle)
# {'account_id':..., 'app_id':..., 'difficulty':..., 'num_solutions':..., 'threshold':..., ...}
# Solve
solution = solver.solve(puzzle)
# Verify a set of solutions locally (hash + uniqueness, not the server signature)
is_valid = solver.verify_solution(puzzle, bytes.fromhex(solution["solutions"]))
# Fetch a live puzzle from a sitekey
puzzle = solver.fetch_puzzle("SITEKEY", endpoint="https://api.friendlycaptcha.com/api/v1/puzzle")
Command Line
# Solve a puzzle string directly
python friendlycaptcha_solver.py --puzzle "a4b457ba....aqhyLQdb..."
# Fetch a live puzzle from a sitekey and solve it
python friendlycaptcha_solver.py --sitekey FCMGEMUD2M567T8G
# EU-only or custom endpoint
python friendlycaptcha_solver.py --sitekey SITEKEY --endpoint https://eu.friendlycaptcha.com/api/v1/puzzle
# JSON output
python friendlycaptcha_solver.py --sitekey FCMGEMUD2M567T8G --output json
Algorithm Reference
See ALGORITHM.md for the full technical specification. Summary:
Puzzle (32-64 byte buffer, base64-encoded, sent as <signature>.<base64>):
4B timestamp | 4B account ID | 4B app ID | 1B version | 1B expiry
| 1B solution count (n) | 1B difficulty (d) | 8B reserved | 8B nonce
| up to 32B optional user data
Difficulty threshold:
T = floor(2^((255.999 - d) / 8))
Solving: pad the buffer with zeroes to 128 bytes. Brute-force the last 8 bytes until:
int.from_bytes(blake2b_256(buffer)[:4], "little") < T
Repeat for n distinct solutions (the official client sets byte 120 of each attempt to the solution index 0..n-1 to partition the search space; the server itself only requires the n final 8-byte values to be distinct and individually valid).
Response payload:
<signature>.<base64 puzzle>.<base64 solutions>.<base64 diagnostics>
submitted as the frc-captcha-solution hidden form field.
API Reference
FriendlyCaptchaSolver
class FriendlyCaptchaSolver:
def __init__(self, max_iterations: int = 2**32):
"""Initialize solver with a per-solution iteration cap."""
def decode_puzzle(self, puzzle: str) -> dict:
"""Parse a '<signature>.<base64>' puzzle string into its fields."""
def solve(self, puzzle: str) -> dict:
"""Solve the puzzle. Returns solution/timing info plus a ready 'response' string."""
def verify_solution(self, puzzle: str, solutions: bytes) -> bool:
"""Locally verify solutions meet the difficulty threshold and are unique."""
@staticmethod
def difficulty_to_threshold(difficulty: int) -> int:
"""T = floor(2^((255.999-d)/8))"""
@staticmethod
def fetch_puzzle(sitekey: str, endpoint: str = DEFAULT_PUZZLE_ENDPOINT) -> str:
"""Fetch a live puzzle string for a sitekey."""
Performance
Measured on this solver (pure Python, single core, hashlib.blake2b):
| Metric | Value |
|---|---|
| Raw hash rate | ~750,000 hashes/sec |
| Official WASM client | ~11,000,000 hashes/sec (~15x faster) |
Real-world puzzles observed (from Friendly Captcha's own production demo widgets):
| Scenario | Difficulty | Threshold | Solutions (n) | Solve time (this tool) |
|---|---|---|---|---|
| Typical | 141-166 | ~2,400-21,000 | 42-48 | ~85-97s |
| "Simulate suspicious user" (playground) | 220 | 22 | 51 | hours (impractical in pure Python) |
Difficulty is chosen by the site owner (and can be raised further for suspicious traffic); the algorithm is identical regardless, this tool just gets proportionally slower. For very high difficulty targets, expect this pure-Python implementation to be a poor fit compared to the official WASM/native solvers.
Validation
This solver was validated against live production infrastructure, not just the written spec:
- Round-trip: fetched a real puzzle from
api.friendlycaptcha.com/api/v1/puzzle(using the sitekey embedded in Friendly Captcha's own homepage), solved all required sub-solutions withsolve(), and confirmedverify_solution()accepts them. - Cross-check against the official client: using an anti-detection browser, loaded the real Developer Playground in v1 mode, let the official WASM widget solve its own puzzle, and fed that genuine solution into this tool's
verify_solution()— confirmed valid, with the widget's own diagnostics byte confirmingsolver_type=2(WASM), so this wasn't our own output being checked against itself. - Parameter robustness: confirmed the puzzle format is unchanged across Widget Mode, Start Mode, Theme, Language, and API Endpoint (Global/EU) settings, and across the "Simulate suspicious user" toggle (which only raises
difficulty/n, not the format).
See examples/verify_algorithm.py for the automated regression tests, including the captured real WASM-client solution used as a permanent test vector.
Limitations
- v1 only — see v1 vs v2. v2's risk/fingerprint signals are not handled.
- Not a bypass of intent — PoW is designed to be solvable by any computer; this tool just automates what a browser would do anyway.
- Signature not forged — the server-side
signatureis opaque and passed through unmodified; this tool cannot forge a valid puzzle, only solve genuine ones issued by the server. - Challenge expiration — puzzles expire (
expirybyte × 300s, commonly 1 hour); solve and submit before that window closes. - Pure Python performance — impractical against very high difficulty puzzles (see Performance).
Security Considerations
What Friendly Captcha provides: bot deterrence via computational cost, no cookies/tracking, EU-only infrastructure option.
What it does NOT provide (v1): human verification, or protection against an attacker willing to spend the CPU time — which is the entire point of PoW-based CAPTCHAs, not a flaw specific to this tool.
License
MIT License - See LICENSE for details.
References
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file friendlycaptcha_solver-1.0.0.tar.gz.
File metadata
- Download URL: friendlycaptcha_solver-1.0.0.tar.gz
- Upload date:
- Size: 10.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98b58bb7c852106ed15734fd57a5f3e80883a7e39bfd9a4310c1106f5892b1b9
|
|
| MD5 |
760c888cbde27e53025f099e8cd54cac
|
|
| BLAKE2b-256 |
aa4bdccd673fed05265eebfcec7e76e52239b681a47b768eba3e46d9c354b9c9
|
Provenance
The following attestation bundles were made for friendlycaptcha_solver-1.0.0.tar.gz:
Publisher:
publish.yml on opastorello/friendlycaptcha-solver
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
friendlycaptcha_solver-1.0.0.tar.gz -
Subject digest:
98b58bb7c852106ed15734fd57a5f3e80883a7e39bfd9a4310c1106f5892b1b9 - Sigstore transparency entry: 2835991121
- Sigstore integration time:
-
Permalink:
opastorello/friendlycaptcha-solver@8e53ab2a6dc531d7b0ac29dcfb1845a1fa5888ca -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/opastorello
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8e53ab2a6dc531d7b0ac29dcfb1845a1fa5888ca -
Trigger Event:
release
-
Statement type:
File details
Details for the file friendlycaptcha_solver-1.0.0-py3-none-any.whl.
File metadata
- Download URL: friendlycaptcha_solver-1.0.0-py3-none-any.whl
- Upload date:
- Size: 10.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
977a6231486caf8e75149ec65e25ff67da3894bc7123ad438c1a4316b0c2bd74
|
|
| MD5 |
0f50ea1b12731a78daeb3c88b8d86c7d
|
|
| BLAKE2b-256 |
8698cdb9711c67e8cc3af056bac8d90a54b11e005e049d2ea7ec40542bd88d88
|
Provenance
The following attestation bundles were made for friendlycaptcha_solver-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on opastorello/friendlycaptcha-solver
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
friendlycaptcha_solver-1.0.0-py3-none-any.whl -
Subject digest:
977a6231486caf8e75149ec65e25ff67da3894bc7123ad438c1a4316b0c2bd74 - Sigstore transparency entry: 2835991145
- Sigstore integration time:
-
Permalink:
opastorello/friendlycaptcha-solver@8e53ab2a6dc531d7b0ac29dcfb1845a1fa5888ca -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/opastorello
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8e53ab2a6dc531d7b0ac29dcfb1845a1fa5888ca -
Trigger Event:
release
-
Statement type: