How does the textCAPTCHA service work?

When you sign up for your own account, you are provided with a unique API key. This key is your unique identifier for this site, like a password. For testing purposes you can use the API key "demo" which will serve a limited subset of questions.

Whenever you require a logic question, you need to make a request to our server. It will return an XML response containing a randomly selected question and answer. Multiple answers may be returned if several user responses are acceptable (e.g. '1' and 'one'). To help prevent malicious users caching the challenge and responses, the answers are hashed using the MD5 algorithm. This allows you to compare a users response with the answers without explicitally knowing the answer yourself.

Below are a couple of examples how you might use the service in a bespoke PHP5 application. Alternatively, you may be able to leverage existing integration implementations:

Basic Usage

The URL for an XML CAPTCHA response is:

http://api.textcaptcha.com/your_api_key

An example of the XML response to such a request is:

<captcha>
<question>If tomorrow is Saturday, what day is today?</question>
<answer>f6f7fec07f372b7bd5eb196bbca0f3f4</answer>
<answer>dfc47c8ef18b4689b982979d05cf4cc6</answer>
</captcha>

In this case, there are two answers permitted: "friday" and "fri". The MD5 hash of both (lower-case) answers are included in the XML response. You are free to use these how you wish in your own implementation. This example uses PHP5, and simpleXML. The first step is to load the captcha, issue the question to the user as part of a form and store the answer in the session variable.

  1. <?php
  2. session_start();
  3. // load captcha using web service
  4. $url = 'http://api.textcaptcha.com/my_api_key';
  5. try {
  6. $xml = @new SimpleXMLElement($url,null,true);
  7. } catch (Exception $e) {
  8. // if there is a problem, use static fallback..
  9. $fallback = '<captcha>'.
  10. '<question>Is ice hot or cold?</question>'.
  11. '<answer>'.md5('cold').'</answer></captcha>';
  12. $xml = new SimpleXMLElement($fallback);
  13. }
  14. // display question as part of form
  15. $question = (string) $xml->question;
  16. // ... [snip] ...
  17. // store answers in session
  18. $ans = array();
  19. foreach ($xml->answer as $hash)
  20. $ans[] = (string) $hash;
  21. $_SESSION['captcha'] = $ans;

Once the form has been submitted, the answer given by the user needs to be validated against the answers that you have stored in session. You need to trim, lower-case and MD5 hash the user's response before directly comparing it to the answers stored in session.

  1. <?php
  2. session_start();
  3. $ans = $_POST['captcha']; // .. or whatever!
  4. $ans = strtolower(trim($ans));
  5. $ans = md5($ans);
  6. if (in_array($ans,$_SESSION['captcha'])) {
  7. // passed..!
  8. } else {
  9. // error: redisplay form, etc.
  10. }

Advanced Usage

It is also possible to embed the answers directly in the form as hidden inputs. However, if you do not 'salt' the direct hash of the answer, this will weaken the strength of your implementation. For example, an attacker might try to guess the correct answer by hashing each word of the question and attempting to match it to the hidden form input answers (which might work for some logic questions). To protect against this, you need to 'salt' the answer hashes:

  1. <?php
  2. $salt = 'MySecretSalt';
  3. // ... [snip] ...
  4. // output hidden form fields
  5. foreach ($xml->answer as $hash) {
  6. $ans = md5($hash.$salt);
  7. echo '<input type="hidden" name="ans[]" value="'.$ans.'" />';
  8. }
  9. // ... [snip] ...
  10. // on submission, validate against hidden fields
  11. $user_ans = $_POST['captcha']; // .. or whatever!
  12. $user_ans = strtolower(trim($user_ans));
  13. $user_ans = md5(md5($user_ans).$salt);
  14. if (in_array($user_ans,$_POST['ans'])) {
  15. // passed..!
  16. } else {
  17. // error: redisplay form, etc.
  18. }

In such a non-session implementation, you should also consider the possibility of an attacker re-using the same captcha tokens repeatedly. For example, if the attacker loads the form and answers the question provided correctly once manually, they can then resend the same form repeatedly and it will pass your captcha test. To prevent this, you will need to lock the captcha provided to a specific form instance, and make sure that form instance can only be utilised once. This is often achieved using form timeouts from hidden inputs, or other techniques designed to avoid CSRF attacks. A full explanation is beyond the scope of this page, but the issue needs careful consideration to provide a robust implementation.

Questions? Contact Rob.