Math CAPTCHA

CAPTCHAs (completely automated public turing tests to tell computers and humans apart) are quick easy ways of preventing bots from using your server software. However the most common form of CAPTCHAs has a number of problems, one of the most serious problems is the accessibility issue.

A different kind of CAPTCHA has been investigated by numerous people, with varying degrees of success. Everything from the KittenAuth method to logic tests has been tried, and one of the more successful ideas has been mathematics.

Simple maths is extremely easy for human beings, and highly accessible because it doesn’t rely on any multimedia support in the users browser, and doesn’t rely on the user having vision.

If you just want the source code, go down to the bottom.

Implementation

We’re going to write it in PHP because PHP is the most widely supported language on web servers.

Firstly we need to consider what we actually want: we want a simple mathematical sum which is easy enough for quick mental arithmetic. We want to express this in human language which has the double benefits of being easier for humans and much much harder for automated attack. Because we want it in human language, we need to make it easily configurable to allow for different languages.

So our first step is to create a configuration array:

$config = array( 'max_attempts' => "5", 'l_exceeded_max_attempts' => "<p>You have exceeded the number of allowable attempts. Please return later.</p>", 'l_failure' => "<p>Sorry, that wasn't the correct answer. Try again:</p>", 'l_operator_addition' => "plus", 'l_operator_subtraction' => "minus", 'l_operator_multiplication' => "multiplied by", 'l_operator_division' => "divided by", 'l_answer' => "Your answer:", 'lowest_num' => "1", 'highest_num' => "5" );

The first variable, max_attempts, is to stop brute force attacks, where bots simply try again and again until they succeed. Our next variable is the language string to output when the maximum number of attempts has been reached.
Similarly, the next variable, l_failure is the language string to output if the user fails to correctly answer the sum.
The remainder are totally self explanatory.

We’re going to be using sessions in this script, to keep an array of useful information easily accessible. So our next call is to the session start function.

session_start();

I like to keep my application code simple, as simple as placing a call to a pre-defined function, so our next step is to conceptualise a function that will do all the things we need it to. This, by the way, is not necessarily the best way of doing this. However, it works well for me.

Our main function is unsuprisingly called math_captcha

function math_captcha() { global $config; // If the user has already passed the captcha, we don't need to bother them: if (isset($_SESSION['math_captcha_passed']) && $_SESSION['math_captcha_passed'] "true") { return true; } // If session attempts variable doesn't exist, set it at zero if (!isset($_SESSION['math_captcha_attempts'])) { $_SESSION['math_captcha_attempts'] = "0"; } // If session attempts variable equals the configured maximum attempts, end script elseif (isset($_SESSION['math_captcha_attempts']) && $_SESSION['math_captcha_attempts'] $config['max_attempts']) { die($config['l_exceeded_max_attempts']); } if (isset($_POST['answer'])) { math_captcha_validate($_POST['answer']); } // Else, we'll create our CAPTCHA else { math_captcha_create(); } }

The first line, global $config, simply gets our configuration array. If you made this into a class this step would be unnecessary, it’s totally up to you.

Our next step checks to see whether our user has already passed the captcha before; one way I’ve just used this function on a client website is to request a user completes a math CAPTCHA on his first post in a forum, but thereafter in that session we don’t want to bother them again.
See the final code at the end for more information on that.

If this is the first time the script has been run this session we need to set up a piece of session information, namely our session attempts variable ($_SESSION['math_captcha_attempts']), and this next statement does that. The next block is closely related to that – we need to check if our user has already tried this a couple of times. If they have, and they’ve exceeded the number of allowable attempts we need to stop executing and tell them so.

If the user has submitted the form we need to process their answer, and so we do so by calling one of our later functions, math_captcha_validate.

If the script has run this far, it means we need to generate a new CAPTCHA for the user, we do this by calling our function math_captcha_create.

So let’s move on to that function:


function math_captcha_create() { global $config; // Set the first number $num1 = math_captcha_num($config[‘lowest_num’], $config[‘highest_num’]); // Set the second number $num2 = math_captcha_num($config[‘lowest_num’], $config[‘highest_num’]); // Generate a random number to choose the operator $opnum = math_captcha_num(“1”, “4”); switch ($opnum) { case 1: $calc = $num1 + $num2; $operator = $config[‘l_operator_addition’]; break; case 2: $calc = $num1 – $num2; $operator = $config[‘l_operator_subtraction’]; break; case 3: $calc = $num1 * $num2; $operator = $config[‘l_operator_multiplication’]; break; case 4: $calc = $num1 / $num2; $operator = $config[‘l_operator_division’]; break; }

// Set the calculation $_SESSION[‘math_captcha_calc’] = math_captcha_salt($calc); $sum_translated = “$num1 $operator $num2”; $output =
<<<output <p>$sum_translated</p> <label for=“answer”>{$config[‘l_answer’]}</label> <input id=“answer” name=“answer” />
output; echo($output);

}

The first block of code, after global $config, set three new variables containing 3 random numbers. For the first two, these will be between our configured lowest and highest numbers, for the third it’ll be between 1 and 4. The first two variables are our two numbers which will eventually be in the sum. The third will simply be used to select between the four major mathematical operations – addition, subtraction, multiplication and division.

This code uses a self rolled random number function. I chose to do it this way because php’s rand function doesn’t produce very random numbers and if I wanted to increase the randomness later, I could do so without modifying much code.
For brevity, I won’t include this basic function here – it’s in the full source code below if you want it.

The next block is a switch block which sets the defintion of our human readable sum based upon the value of our opnum variable. It also calculates the answer.

Now we come to a very important step – we store the calculated value of our sum in a session key, but first we run it through an encryption sequence. I’ve seen a number of security systems make an elementary mistake of just running it through md5 – there are a number of databases of md5 hashed information around now – md5 is not safe.
Worse still a few times I’ve seen answers and passwords stored in plain text!

Finally we produce our human readable sum and output it to the browser.

Our encryption function, math_captcha_salt:
function math_captcha_salt($in) { // This is our salt $unencrypted_date = date('d'); $date = md5($unencrypted_date); $md5 = md5($in); // joins the two strings $out = $md5.$date; return $out; }

This is a simple function which simply md5 hashes the exact date (for example ‘12’ for February the 12th) and appends it to the information that we want hashed, in this case the pre-calclated value of our sum, which has also been md5 hashed.

The date here acts as a basic cryptographic salt and also gives the resultant value a very short life. This has the added bonus of reducing the scope for reuse and damage if the security is broken through.

You can easily modify this sequence too; change the argument for the PHP date function, flip over the $md5 and $date functions on the line beginning $out. Everything will still work as expected elsewhere.

Our final function, math_captcha_validate:

// Validates user input
function math_captcha_validate($answer) {

if ($_SESSION[‘math_captcha_calc’] == math_captcha_salt($answer)) { $_SESSION[‘math_captcha_passed’] = “true”; } else { $_SESSION[‘math_captcha_attempts’] = $_SESSION[‘math_captcha_attempts’] + 1; echo($config[‘l_failure’]); math_captcha_create(); }
}

The first block, the if statement, evaluates whether the answer given by our user, once salted, is equal to the math_captcha_calc session key. If so, it sets our session math_captcha_passed key and the script ends.

If not, the user has answered wrongly so we need to add one to the value of $_SESSION[‘math_captcha_attempts’], inform the user that they have failed the test and give them another chance.

That’s it folks. Download the example source code.

Related articles