Ashley Sheridan​.co.uk

PHP Captcha

Posted on

Spam is a huge problem on the Internet today, across the Web, Email, Newsgroups, to name but a few areas. Recently, spammers have been targeting websites, particularly forums, blogs and wikis. The tried and tested method to prevent this is to use a CAPTCHA. The idea goes back to Alan Turings idea of a test to differentiate humans and computers, which is now used to test AI. 1997, Andrei Broder devised the first captcha for AltaVista. It used anti-OCR techniques to prevent spam bots from progressing past a given point on a website.

Today's captcha's have changed little in their reason, but their methods have improved considerably. What I have here is a simple captcha script that creates a simple image captcha by selecting several random characters from the alphabet, and alters their size and orientation. To improve the effect some more, random lines have been added to the image, and a font has been used which should make it a little more difficult for a spam bot to discern the text. The image itself looks like this, and the code which produces it is below:

Captcha - 'IADSW' <?php function insertCaptcha() { $image = imagecreatetruecolor(300, 100); $background = imagecolorallocate($image, 255, 255, 255); $foreground = imagecolorallocate($image, 88, 88, 88); imagefill($image, 0, 0, $background); $string = ''; // get 5 random characters, and perform one-way encryption for($i=0; $i<5; $i++) { $char = chr(rand(65, 90)); $angle = rand(-30, 30); imagettftext($image, 40, $angle, ($i * 50 + 50), 50, $foreground, 'captcha/Cacophony Out Loud.ttf', $char); $string .= $char; } // encrypt the string $crypted = crypt($string); // add a random selection of lines across the image for($i=0; $i<10; $i++) { $y1 = rand(0, 100); $y2 = rand(0, 100); $x1 = rand(0, 350); $x2 = rand(0, 350); imageline($image, $x1, $y1, $x2, $y2, $foreground); } // this is only available on hosting that has PHP5 or later //imagefilter($image, IMG_FILTER_GAUSSIAN_BLUR); // render image $imageout = imagepng($image, 'captcha/captcha.png'); $now = date("ymdHis"); // output image and form field print '<img src="captcha/captcha.png?t=' . $now . '" alt=""/>'; print '<input type="hidden" name="captchacode" value="' . $crypted . '"/>'; print '<div><label for="captcha">Captcha</label>'; print '<input type="text" id="captcha" name="captcha" class="text"/>'; print '</div>'; print 'Type in the code you see in the image exactly as it is. Please note that all characters are uppercase, and you should enter them so.'; } ?>

You should either have this as its own file, or add the function to an existing PHP file, ideally an include file in your site.

Lines 4 through 7 create a new image, allocate the 2 colours that will be used, and fill it with the background colour. The string created at line 9 will be used to hold all the characters that are displayed, and the loop at line 12 continually adds to this variable as it creates random characters and output them to the image.

Within the loop, line 14 creates a random letter in the range 65-90, the uppercase range of ASCII. Line 15 sets a random angle within a 60° range; any more than this, and it starts getting confusing for people with certain leters.

Line 16 pulls the above variables together and outputs the letter to the image object, and the loop continues. Line 22 creates an encrypted version of the string, as we can't send the plain text version with the form, as this would be easily read to bypass the captcha.

The loop at line 25 creates some random lines which are displayed across the image. I've selected 10 for the example, but you can have more or less to suit your requirements.

You may notice that at line 36, I've commented out the imagefilter call. This is because it is a PHP5 function, and as yet, not all web hosts have upgraded from version 4, even when PHP6 has just been released! If your hosting supports it, you can uncomment this line to add a blur to the image, which should confuse a spam bot just that little bit more.

The image file is created at line 39. Note, that while this method is fine for small sites where traffic is at a minumum, you should look to some other method of naming files for busier sites, as potentially the code could be called by a second user before the image has fully downloaded to the first. I'd suggest using the date to name files, which makes cleaning them up automatically a lot easier afterwards.

To prevent the browser from caching the image, I've added the date created in line 41 to the URL as a query string. This should force the browser to reload the image each time it is called.

The print statements simply output the HTML for the image and form elements. The encrypted captcha is stored in the hidden form field to be sent when the form is submitted. Note, that although this uses a one-way encryption, which cannot be decrypted by PHP, it is not infallible. If you require a more secure method, either use stronger encryption, or store the string on the server, and use the session id to reference it.

The call to the function should be placed inside a form output by PHP, so that it creates the proper HTML code. To compare the user string with the encrypted version, you should encrypt the user string, using the encrypted string as a key. Sound confusing? This should help:

$captcha = $_POST['captcha']; $captchacode = $_POST['captchacode']; if(crypt($captcha, $captchacode) == $captchacode) {// codes match} else {// codes do not match}

In this example, $captcha is the user-entered value, and $captchacode is the encrypted version. By passing two arguments to the crypt function, you use the encrypted string as a key for generating the second encrypted string. If the two match, the user typed it correctly, if not, they didn't. Inside your if/else statement you can send the visitor elsewhere in your site, either to retry the form, or to a success page.

The only caveat of this script is the failure to accomodate blind or visually impaired visitors. For a time this has caused me quite a dilemma, but I think I may have found the answer in symbolic links. Once I've developed it further, I'll add it in as another PHP script, so you should look for that in the near future.