UltraMega Blog
20Dec/089

Creating a CAPTCHA in PHP with GD

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 Example

So let's get started. First, we must generated our random code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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:

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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:

33
34
35
36
37
38
39
40
41
42
43
44
45
// 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:

46
47
48
49
50
51
52
53
54
55
// 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:

56
57
58
59
60
61
62
63
64
// 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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:

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.

See it in action

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.

Similar Posts:

 

About Steve

Steve is the owner of UltraMega Tech. He is a freelance Web designer and developer who specializes in PHP and AJAX development.
Comments (9) Trackbacks (0)
  1. i copied both files in the same directory but the image doesn’t show up. Only the alt text and the broken image logo is shown. btw, GD is enabled. what could be the problem? Help needed !

    • Do the examples on this website work for you? What happens when you browse directly to captcha.php? Are there any errors displayed? If it’s blank, there may be errors logged somewhere that will help find the problem.

  2. the problem is in this:

    line 42, 43

    // “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));
    }

    replace arial.ttf with .ttf file you have and it will work :)

  3. Here is the error I am getting. I can’t get the captcha to appear. I only see alt text and a broken image.

    Warning: session_start() [function.session-start]: Cannot send session cache limiter – headers already sent (output started at /home/emsmcs/public_html/captcha.php:2) in /home/emsmcs/public_html/captcha.php on line 4

    Warning: Cannot modify header information – headers already sent by (output started at /home/emsmcs/public_html/captcha.php:2) in /home/emsmcs/public_html/captcha.php on line 58

    Warning: Cannot modify header information – headers already sent by (output started at /home/emsmcs/public_html/captcha.php:2) in /home/emsmcs/public_html/captcha.php on line 59

    Warning: Cannot modify header information – headers already sent by (output started at /home/emsmcs/public_html/captcha.php:2) in /home/emsmcs/public_html/captcha.php on line 62

  4. BTW gd is enabled and the ttf is uploaded.

  5. hi

    first,thanks for this tutorial. i have one problem: the captcha is generated and re-generated on each page refresh, which is good.
    but the form refuse to appear on the browser. i have puted all the code on the same file, am i right?

    what am i missing here?

    hope for help:)

    • The file that generates the image must be in a separate file from everything else. It is included as an image in your form HTML markup. So you should have at least 2 PHP files.

  6. Good script. Works Excellent. thanks


Leave a comment


No trackbacks yet.

Page optimized by WP Minify WordPress Plugin