Skip to main content

Friendly Captcha Solver

CI PyPI version Python 3.6+ License: MIT

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

  • 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-pow repository
  • 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.isTrusted on every observed event — which is impossible to forge via dispatchEvent(), 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:

  1. 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 with solve(), and confirmed verify_solution() accepts them.
  2. 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 confirming solver_type=2 (WASM), so this wasn't our own output being checked against itself.
  3. 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 signature is opaque and passed through unmodified; this tool cannot forge a valid puzzle, only solve genuine ones issued by the server.
  • Challenge expiration — puzzles expire (expiry byte × 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

friendlycaptcha_solver-1.0.0.tar.gz (10.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

friendlycaptcha_solver-1.0.0-py3-none-any.whl (10.6 kB view details)

Uploaded Python 3

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

Hashes for friendlycaptcha_solver-1.0.0.tar.gz
Algorithm Hash digest
SHA256 98b58bb7c852106ed15734fd57a5f3e80883a7e39bfd9a4310c1106f5892b1b9
MD5 760c888cbde27e53025f099e8cd54cac
BLAKE2b-256 aa4bdccd673fed05265eebfcec7e76e52239b681a47b768eba3e46d9c354b9c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for friendlycaptcha_solver-1.0.0.tar.gz:

Publisher: publish.yml on opastorello/friendlycaptcha-solver

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file friendlycaptcha_solver-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for friendlycaptcha_solver-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 977a6231486caf8e75149ec65e25ff67da3894bc7123ad438c1a4316b0c2bd74
MD5 0f50ea1b12731a78daeb3c88b8d86c7d
BLAKE2b-256 8698cdb9711c67e8cc3af056bac8d90a54b11e005e049d2ea7ec40542bd88d88

See more details on using hashes here.

Provenance

The following attestation bundles were made for friendlycaptcha_solver-1.0.0-py3-none-any.whl:

Publisher: publish.yml on opastorello/friendlycaptcha-solver

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page