Yesterday I sat down and remonissed about a simple web site that I put together using Coldfusion. Not too long after the site was published on the internet, the guest book that I had built was beginning to become filled with spam.
Long story short, I realized that I should have built in a captcha (Completely Automated Public Turing Test to tell Computers and Humans Apart) method to make the spammer’s jobs a little more difficult.
You can learn a little more about captcha here.
In this series of posts, I will attempt to develop a simple captcha method that might be used in a guest book, blog or whatever else we can imagine we would want to keep spammers out.
First things first, I want to layout some guidelines for the simple project.
- PHP will be used for the server side language (for the sake of my sanity and to cut down on discussions regarding OOP and procedural programming, I will use the latter in this example and append the former in a later follow-up post)
- PHP will dynamically generate the captcha image
- The captcha challenge code will be saved to something like a session, cookie or database
- Javascript will be used as the client side language
- Using Ajax, we can request the captcha image and test the user’s response by enhancing the original PHP script
- All screen shots will be from Firefox
- Don’t get me started on IE
- This little project will not attempt to solve every possible spam attack, just make their jobs harder
- Read, I am attempting to demonstrate how easy it is to start on the journey of freeing yourself from spam by building a captcha system from scratch
Ok, now that we have the programming languages chosen, it is time to decide how we will code the functions. Since I prefer to keep everything neat, I will break each task into its own function.
Open a text editor, and follow along as I build the server side functions.
captcha.php
function generateMD5String($chars = 5)
{
//Generate a random md5 code $chars long
$md5 = md5(microtime() * mktime());
$string = substr($md5,0,$chars);
return $string;
}
Simple, we have a nice little function that will generate a random string of letters and or numbers X characters long. Now we can move on to the fun stuff. Next I am going to build a quick function that will use the libgd functions of PHP to build a simple rectangle shaped image, and write the challenge string from our generateMD5String() function overtop the image.
captcha.php cont …
function generateCaptcha($recHeight = 200, $recWidth = 75)
{
$challenge_string = generateMD5String();
//Set the header for the PNG
header("Content-type: image/png");
$im = imagecreatetruecolor($recHeight, $recWidth);
//Let's make the font black (0,0,0)
$font_color = imagecolorallocate($im, 0, 0, 0);
//For fun I have set the background to a greyish color
$im_background = imagecolorallocate($im, 229, 229, 229);
//Fill in the rectangle with color
imagefilledrectangle($im, 0, 0, $recHeight, $recWidth, $im_background);
//Load the gdf to display the challenge word
$font = imageloadfont("./fonts/alienation48.gdf");
imagestring($im, $font, rand(0, $recHeight/3), rand(0, $recWidth/3), $challenge_string, $font_color);
//Display the image
imagepng($im);
}
The generateCaptcha() function will draw a png with our challenge string from the generateMD5String() and position the text randomly.
You may have noticed that I used the imageloadfont() function to use a font other than the default. A quick search on the internet can provide you with a listing of gdfs that you can use in this script.
So our captcha is looking pretty good so far. To enhance our method, I thought it would be a good idea to draw several lines throughout the png that we are dynamically generating.
captcha.php cont…
function drawRandomLines($im, $recHeight, $recWidth, $lines = 50)
{
//Add $lines lines to the image with random x, y coordinates
//within the $recHeight, $recWidth values
for($i = 1; $i <= $lines; $i++)
{
$line = imagecolorallocate($im, 207, 207, 207);
imageline($im, rand(0, $recHeight), rand(0, $recWidth), rand(0, $recHeight), rand(0, $recWidth), $line);
}
}
The drawRandomLines() function will draw (by default) 50 lines with random start and end positions on our image. To ensure that the lines overlap the font, call the drawRandomLines() function after you have used the imagestring() function.
Below is the complete PHP code that generates our new captcha image:
captcha.php complete
<?php
function generateMD5String($chars = 5)
{
//Generate a random md5 code $chars long
$md5 = md5(microtime() * mktime());
$string = substr($md5,0,$chars);
return $string;
}
function drawRandomLines($im, $recHeight, $recWidth, $lines = 50)
{
//Add $lines lines to the image with random x, y coordinates
//within the $recHeight, $recWidth values
for($i = 1; $i <= $lines; $i++)
{
$line = imagecolorallocate($im, 207, 207, 207);
imageline($im, rand(0, $recHeight), rand(0, $recWidth), rand(0, $recHeight), rand(0, $recWidth), $line);
}
}
function generateCaptcha($recHeight = 200, $recWidth = 75)
{
$challenge_string = generateMD5String();
//Set the header for the PNG
header("Content-type: image/png");
$im = imagecreatetruecolor($recHeight, $recWidth);
//Let's make the font black (0,0,0)
$font_color = imagecolorallocate($im, 0, 0, 0);
//For fun I have set the background to a greyish color
$im_background = imagecolorallocate($im, 229, 229, 229);
//Fill in the rectangle with color
imagefilledrectangle($im, 0, 0, $recHeight, $recWidth, $im_background);
//Load the gdf to display the challenge word
$font = imageloadfont("./fonts/alienation48.gdf");
imagestring($im, $font, rand(0, $recHeight/3), rand(0, $recWidth/3), $challenge_string, $font_color);
//To add some further complexity, I want to draw lines across the random text
drawRandomLines($im, $recHeight, $recWidth);
//Display the image
imagepng($im);
}
generateCaptcha();
?>
The resulting image from our new captcha script:
In Part 02 of this series of posts, I will enhance the PHP script to support saving the captcha challenge code to the database.