How to create CAPTCHA using GD in php

Post Reply
User avatar
Neo
Site Admin
Site Admin
Posts: 2642
Joined: Wed Jul 15, 2009 2:07 am
Location: Colombo

How to create CAPTCHA using GD in php

Post by Neo » Mon Mar 01, 2010 12:53 am

A CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) is a system designed to test if input is originating from a human or a computer. The most common method, which you have probably seen, is displaying an image containing distorted text and asking the user to type in the text. It is difficult for a computer to read it and relatively easy for humans, so it is assumed that a correct answer must have originated from a human. This is a tool used to prevent automated spam.

Anyway, this tutorial will explain how to make your own CAPTCHA like the one below using PHP and the bundled GD image manipulation library. This is the method I use on many projects, and it does the job. Keep in mind that there are stronger CAPTCHA systems available if you want to block the more motivated spammers.

Here's an example:
captcha.gif
captcha.gif (1.74 KiB) Viewed 3316 times
So let's get started. First, we must generated our random code:

Code: Select all

<?php
// start the session to store the variable
session_start();
 
// generate the random code
$chars = 'abcdefghkmnprstuvwxyzABCDEFGHJKLMNPQRSTUV2345689';
$length = 6;
$code = '';
for($i = 0; $i < $length; $i++){
   $pos = mt_rand(0, strlen($chars)-1);
   $code .= substr($chars, $pos, 1);
}
 
// store the code to compare later
$_SESSION['captcha'] = $code;
?>
Here, we generated a random 6 digit code from the list of characters. Characters that tend to look similar were removed to make it easier for people to read. We also started a session and stored the code in a session variable. Sessions are basically a way to store variable between page loads. This way, the script can know what the original code was without the client knowing.

Note: The mt_rand($min, $max) function generates a random integer, and we will be using it a lot in this script. Specifying the min and max parameters will limit the output to those boundaries.

Now it's time to create the actual image:

Code: Select all

// set up the image
// size
$width = 120;
$height = 30;
// colors
$r = mt_rand(160, 255);
$g = mt_rand(160, 255);
$b = mt_rand(160, 255);
// create handle for new image
$image = imagecreate($width, $height);
// create color handles
$background = imagecolorallocate($image, $r, $g, $b);
$text = imagecolorallocate($image, $r-128, $g-128, $b-128);
// fill the background
imagefill($image, 0, 0, $background);
 

You can read the comments to figure out what each part does. Basically we created an image, set the dimensions, created the colors with random RGB values, and filled the background. To make sure the background and text aren't the same color, we used the same RGB values minus 128.

We have an image of a solid background, so now it's time to add the code:

Code: Select all

// add characters in random orientation
for($i = 1; $i <= $length; $i++){
   $counter = mt_rand(0, 1);
   if ($counter == 0){
      $angle = mt_rand(0, 30);
   }
   if ($counter == 1){
      $angle = mt_rand(330, 360);
   }
   // "arial.ttf" can be replaced by any TTF font file stored in the same directory as the script
   imagettftext($image, mt_rand(14, 18), $angle, ($i * 18)-8, mt_rand(20, 25), $text, "arial.ttf", substr($code, ($i - 1), 1));
}
 
Here, as we loop through each character, we randomly decide to rotate it clockwise or counter-clockwise by a random degree, then we add it the the image. The imagettftext() accepts 8 parameters: the image handle, font size, the angle of the text, the x position (of the lower left corner of the first character), the y position (of the font's baseline), the color handle, the TTF font file, and the text string. You can use any TTF font file and place it in the same directory or the GD include path. Windows users can usually find their system fonts in Control Panel > Fonts.

Now that we have our code, lets add some effects to help obscure the code:

Code: Select all

// draw a line through the text
imageline($image, 0, mt_rand(5, $height-5), $width, mt_rand(5, $height-5), $text);
 
// blur the image
$gaussian = array(array(1.0, 2.0, 1.0), array(2.0, 4.0, 2.0), array(1.0, 2.0, 1.0));
imageconvolution($image, $gaussian, 16, 0);
 
// add a border for looks
imagerectangle($image, 0, 0, $width - 1, $height - 1, $text);
 
Here, we added a line through the text to make it harder for computers to tell the characters apart, we then applied a Gaussian blur to make it all blend in. We also added a border for looks.

Now that the image is created, it's time to output it to the browser:

Code: Select all

// prevent caching
header('Expires: Tue, 08 Oct 1991 00:00:00 GMT');
header('Cache-Control: no-cache, must-revalidate');
 
// output the image
header("Content-Type: image/gif");
imagegif($image);
imagedestroy($image); 
 
After setting the no-cache headers and setting the content type to a GIF image, we sent the image and cleared it from memory using imagegif() and imagedestroy().

Here is the complete script:

Code: Select all

<?php
// start the session to store the variable
session_start();
 
// generate the random code
$chars = 'abcdefghkmnprstuvwxyzABCDEFGHJKLMNPQRSTUV2345689';
$length = 6;
$code = '';
for($i = 0; $i < $length; $i++){
   $pos = mt_rand(0, strlen($chars)-1);
   $code .= substr($chars, $pos, 1);
}
 
// store the code to compare later
$_SESSION['captcha'] = $code;
 
// set up the image
// size
$width = 120;
$height = 30;
// colors
$r = mt_rand(160, 255);
$g = mt_rand(160, 255);
$b = mt_rand(160, 255);
// create handle for new image
$image = imagecreate($width, $height);
// create color handles
$background = imagecolorallocate($image, $r, $g, $b);
$text = imagecolorallocate($image, $r-128, $g-128, $b-128);
// fill the background
imagefill($image, 0, 0, $background);
 
// add characters in random orientation
for($i = 1; $i <= $length; $i++){
   $counter = mt_rand(0, 1);
   if ($counter == 0){
      $angle = mt_rand(0, 30);
   }
   if ($counter == 1){
      $angle = mt_rand(330, 360);
   }
   // "arial.ttf" can be replaced by any TTF font file stored in the same directory as the script
   imagettftext($image, mt_rand(14, 18), $angle, ($i * 18)-8, mt_rand(20, 25), $text, "arial.ttf", substr($code, ($i - 1), 1));
}
 
// draw a line through the text
imageline($image, 0, mt_rand(5, $height-5), $width, mt_rand(5, $height-5), $text);
 
// blur the image
$gaussian = array(array(1.0, 2.0, 1.0), array(2.0, 4.0, 2.0), array(1.0, 2.0, 1.0));
imageconvolution($image, $gaussian, 16, 0);
 
// add a border for looks
imagerectangle($image, 0, 0, $width - 1, $height - 1, $text);
 
// prevent caching
header('Expires: Tue, 08 Oct 1991 00:00:00 GMT');
header('Cache-Control: no-cache, must-revalidate');
 
// output the image
header("Content-Type: image/gif");
imagegif($image);
imagedestroy($image); 
?>
So now that we have our CAPTCHA generator, how do we use it? It's really simple; take this example:

Code: Select all

<?php session_start(); ?>
<html>
<head>
<title>CATCHA Test</title>
</head>
<body>
<?php
function valid_captcha($input) {
    $code = $_SESSION['captcha'];
    unset($_SESSION['captcha']);
    return (strcasecmp($input, $code) == 0);
}
if(isset($_POST['captcha'])) {
    if(valid_captcha($_POST['captcha'])){
        echo '<div>Success</div>';
    }
    else{
        echo '<div>Incorrect</div>';
    }
}
?>
<img src="captcha.php" alt="CAPTCHA" width="120" height="30">
<form method="post">
  <input type="text" name="captcha" id="captcha">
  <input type="submit" name="submit" id="submit" value="Test">
</form>
</body>
</html>
Here is the validation function:

Code: Select all

function valid_captcha($input) {
    $code = $_SESSION['captcha'];
    unset($_SESSION['captcha']);
    return (strcasecmp($input, $code) == 0);
} 
Basically, we include the script as you would an image with the img tag, and we have a captcha field in our form. The comparison is done using the valid_captcha() function, which matches the input against the previously stored session variable. It uses the case-insensitive strcasecmp() function so users don't have to worry about capitalization. The function also unsets the session variable so the same code can't be reused. Also be sure to include session_start() on any page using CAPTCHA so we don't lose our session data.

That concludes this tutorial. Thanks for reading, and I hope this is useful! You may also be interested in this post on Reloading Images Using JavaScript.

Edit: Fixed a potential exploit in the implementation. The new comparison function destroys the session variable so the same CAPTCHA can't be reused.
Post Reply

Return to “PHP & MySQL”