Bots & traffic

8 min readUpdated

Mathematical CAPTCHA: how it works, how to build one and where it fails

Quick answer

A mathematical CAPTCHA asks the visitor to solve a small sum, such as 4 plus 7, before a form is accepted. It stops only the crudest form bots: any script that can read the question can calculate the answer, and arithmetic is a barrier for people with dyscalculia. Built well, the answer stays on the server, each question works once, and it sits behind lighter checks such as honeypot fields and rate limits.

01What is a mathematical CAPTCHA?

A mathematical CAPTCHA, also called a math or arithmetic CAPTCHA, is a CAPTCHA whose challenge is a small calculation, such as 'What is 4 plus 7?', that the visitor must answer before a form is accepted. Variants write the numbers as words, render the sum as an image, or swap arithmetic for a simple logic question.

Its appeal is practical. It needs no third-party script, sets no tracking cookies, works without JavaScript and takes a few lines of server code, which is why it is common on the contact, comment and newsletter forms of small sites. What it buys in security is far less than it appears.

02Why are math CAPTCHAs easy for bots?

Because a question a person can read is a question a program can read. A sum written as text is trivial for a script to parse and calculate; rendering it as an image only adds a character recognition step, and published attacks on distorted text reach under a second per image at up to 99.8% accuracy. Worded questions such as 'seven plus two' are no harder for software that reads text.

The research record is old and consistent. In 2010 Hernandez-Castro and Ribagorda showed in Computers & Security that a deployed math CAPTCHA could be completely solved by a low-cost attack on its design and implementation flaws, with no character recognition or artificial intelligence involved. The W3C's note on the inaccessibility of CAPTCHA says logic problems can be largely cracked by simple as well as complex algorithms, and that puzzles at best raise the bar enough that bots move elsewhere.

A small answer space adds a second weakness. With two numbers from 1 to 9 there are only 17 possible sums, and the most likely one, 10, comes up in 9 of the 81 combinations. Even without reading the question, guessing the most common sum succeeds 11.1% of the time, so a design that accepts several guesses per question is weaker still.

Implementation mistakeWhat it allowsFix
The answer, or a hash of it, is in the page, or the check runs only in JavaScriptAny client can read the answer or skip the checkKeep the answer on the server and verify every submission there
A solved question can be submitted againOne solve is replayed for thousands of postsMake each question single-use
Several answers are accepted per questionGuessing through the small answer spaceBurn the question on the first attempt, right or wrong
Questions never expireSolved questions are stockpiled for laterExpire questions after a few minutes
A question works on any formAn easy form supplies passes for a valuable oneBind each question to one form or session

03Are math CAPTCHAs accessible?

Less than they look. WCAG 2.2 defines a cognitive function test as a task to remember, manipulate or transcribe information and names the performance of calculations as an example. Under success criterion 3.3.8 at level AA, such a test may not be required in any step of a login unless another method or a helping mechanism is available, and the W3C's Understanding document uses a math CAPTCHA as an example of a test that is not excepted.

On a contact or comment form, which is not authentication, 3.3.8 does not apply, but the barrier remains. The W3C note on CAPTCHA points out that someone living with dyscalculia will find even simple arithmetic challenging, and recommends offering a different kind of puzzle on request. A sum rendered only as an image also shuts out screen-reader users unless it has a text alternative, and a text alternative that states the sum removes the point of the image.

  • Never put a math CAPTCHA in front of a login without another way to sign in
  • Write the question as real text with a proper label, not as an image
  • Use a numeric keyboard on phones, and accept the answer with surrounding spaces
  • Offer a different kind of question, or a contact route, for anyone who cannot answer
  • Explain a wrong answer clearly and keep the rest of the form filled in

04How to build a math CAPTCHA on the server

Two designs work. The simplest stores the answer in the visitor's server-side session and deletes it on the first attempt. The stateless design below suits pages served from a cache or from several servers: the question travels with a signed token, so no session is needed, and a small shared record of used tokens provides the single-use guarantee.

The token carries an expiry, a random nonce and two HMAC-SHA256 signatures. The first proves the server issued the token for this form; the second binds the correct answer without revealing it, because without the secret key nobody can compute the signature for a guess. Verification checks the first signature, rejects expired or already used tokens, marks the nonce as used before comparing, and compares with hmac.compare_digest, which Python documents as designed to prevent timing analysis. The numbers come from the secrets module, which Python recommends over random for anything security-related.

A minimal stateless math CAPTCHA with single-use tokens (Python 3.11, standard library only; tested)
import hashlib, hmac, secrets, time

KEY = secrets.token_bytes(32)   # in production: a fixed secret loaded from config
TTL = 600                       # seconds a question stays valid
used = {}                       # nonce -> expiry; in production: a shared store with expiry

def _mac(*parts):
    msg = "|".join(map(str, parts)).encode()
    return hmac.new(KEY, msg, hashlib.sha256).hexdigest()

def new_challenge(form):
    a, b = secrets.randbelow(9) + 1, secrets.randbelow(9) + 1
    exp, nonce = int(time.time()) + TTL, secrets.token_hex(16)
    tag = _mac("issued", exp, nonce, form)         # proves this server issued the token
    sig = _mac("answer", a + b, exp, nonce, form)  # binds the answer without revealing it
    return f"What is {a} plus {b}?", f"{exp}.{nonce}.{tag}.{sig}"

def verify(token, answer, form):
    if not token.isascii():
        return False                               # compare_digest raises on non-ASCII text
    try:
        exp_s, nonce, tag, sig = token.split(".")
        exp, guess = int(exp_s), int(answer.strip())
    except ValueError:
        return False
    now = time.time()
    if not hmac.compare_digest(tag, _mac("issued", exp, nonce, form)):
        return False                               # forged, altered or other form
    if exp < now or nonce in used:
        return False                               # expired or already tried
    used[nonce] = exp                              # burn first: one attempt per question
    for n in [n for n, e in used.items() if e < now]:
        del used[n]
    return hmac.compare_digest(sig, _mac("answer", guess, exp, nonce, form))

05What to use instead of, or before, a math CAPTCHA

Order the checks by what they cost real visitors. The cheapest stop most unsophisticated form bots on their own, and each later step should only see what the earlier ones let through. OWASP's bot management cheat sheet describes the hidden honeypot field in detail and treats visible CAPTCHAs as a last resort.

CheckCost to real visitorsWhat it stopsWeakness
Honeypot fieldNone: the field is hidden from people and from assistive technologyBots that fill in every fieldBots that skip hidden fields; browser autofill can occasionally fill it
Time trapNone at normal typing speedInstant scripted submissionsScripts that simply wait
Rate limits per address and accountNone in normal useBulk submissions from a few sourcesTraffic spread over many addresses
Server-side content checksOccasional misfires on messages with linksLink spam and known spam textNeeds tuning and review
Proof-of-work challengeA short pause, nothing to solveMakes cheap high-volume automation costlierDoes not prove a human; real browsers pass
Math CAPTCHAA small task, and a barrier for some disabilitiesNaive form botsAny client that reads the question
Interactive third-party CAPTCHAA task, plus a third-party scriptMore capable botsSolving services, plus privacy and accessibility costs

06When is a math CAPTCHA the right choice?

Rarely as the only control, sometimes as a fallback. It suits low-value forms on small sites that want no third-party code, ideally shown only after a honeypot or rate limit has flagged a submission. It does not suit anything an attacker can profit from.

SituationMath CAPTCHA?Better option
Contact or comment form on a small siteAcceptable as a fallbackHoneypot and time trap first; the math question only for flagged posts
Login formNoRate limits, MFA and breached-password checks, since WCAG 3.3.8 rules out puzzles without an alternative; see credential stuffing
Sign-up form hit by bulk registrationsNoEmail confirmation, rate limits and a background challenge
Checkout or payment formNoThe payment provider's fraud checks and per-card limits
A site flooded with requestsNoFiltering in front of the origin; see HTTP floods

07What a WAF or reverse proxy can add

A web application firewall or reverse proxy in front of the site can take load and abuse off the form before the application sees it, but it cannot judge whether a message is spam.

  • Can: rate-limit submissions to the form's URL per client and refuse request methods the form does not use
  • Can: challenge clients that look automated before they reach the form, so the math question is rarely needed
  • Can: refuse sources already seen abusing other sites behind the same service
  • Cannot: read the meaning of a message, or tell an angry customer from a spammer
  • Cannot: repair a CAPTCHA whose answer leaks in the page or whose tokens can be replayed

Questions

Frequently asked questions

Only against the simplest bots. The question has to be readable by people, so any script that reads it can compute the answer, and researchers have fully broken deployed math CAPTCHAs without any character recognition. It is reasonable as a light fallback on low-value forms, verified on the server with single-use questions, but not as protection for logins, sign-ups or payments.

Yes. Parsing and calculating a written sum takes a script a fraction of a second, rendering it as an image only adds character recognition, and language models read worded questions. That is why a math CAPTCHA should never be the only control on a form that attackers can profit from, and why rate limits and other checks should come first.

Written as plain text they are easier for screen-reader users than distorted images, but they remain a cognitive barrier, especially for people with dyscalculia. WCAG 2.2 counts calculations as a cognitive function test, so a math CAPTCHA on a login fails level AA unless another way to sign in is offered. Always provide a different question or a contact route.

No. An image stops only scripts that skip character recognition, and recognising a few clean digits is easy for modern software. It also hides the question from screen readers, and an honest text alternative would state the sum anyway. Plain text with a clear label is more accessible and barely weaker, so the protection has to come from server-side verification.

Long enough to fill in the form and short enough that solved questions cannot be stockpiled; the example on this page uses ten minutes, which suits a short form. More important than the exact lifetime is that each question works only once: mark it used on the first attempt, right or wrong, and tie it to the form it was issued for.

No. A math CAPTCHA can be generated and checked entirely on the site's own server, with the question in the page and the answer verified when the form arrives. With the stateless design no cookie is needed at all, which is one of its few real advantages over third-party CAPTCHA services that load external scripts and collect browser signals.

Sources

Sources and verification

The Python example was run with Python 3.11.2 on Debian 12 in October 2026: a correct answer passed once, while replays, wrong answers, retries after a wrong answer, expired or altered tokens, tokens containing non-ASCII characters and tokens used on another form were rejected. The guessing figure was computed by enumerating all 81 pairs.

  1. 01Web Content Accessibility Guidelines (WCAG) 2.2: cognitive function test · W3C
  2. 02Understanding Success Criterion 3.3.8: Accessible Authentication (Minimum) · W3C WAI
  3. 03Inaccessibility of CAPTCHA: Alternatives to Visual Turing Tests on the Web (section 2.1.3, Logic Puzzles) · W3C
  4. 04Pitfalls in CAPTCHA Design and Implementation: The Math CAPTCHA, a Case Study (abstract) · Universidad Carlos III de Madrid
  5. 05Hernandez-Castro and Ribagorda, Computers & Security 29 (2010) 141-157 · Elsevier
  6. 06An Empirical Study & Evaluation of Modern CAPTCHAs (Searles et al., USENIX Security 2023) · USENIX
  7. 07Bot Management and Anti-Automation Cheat Sheet · OWASP Cheat Sheet Series
  8. 08hmac: Keyed-Hashing for Message Authentication · Python documentation
  9. 09secrets: Generate secure random numbers for managing secrets · Python documentation

Last updated