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:
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.
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:
- Create a Slide-In Panel with jQuery
- Add Records to a Queue with jQuery
- Easy Text Validation Without Regular Expressions

April 5th, 2009 - 03:35
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 !
April 5th, 2009 - 09:36
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.
June 22nd, 2009 - 20:52
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
January 11th, 2010 - 17:16
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
January 11th, 2010 - 17:40
Make sure there is no text before <?php in the beginning of captcha.php. This includes any whitespace.
January 11th, 2010 - 17:24
BTW gd is enabled and the ttf is uploaded.
February 7th, 2010 - 09:09
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:)
February 8th, 2010 - 11:37
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.
May 10th, 2010 - 12:09
Good script. Works Excellent. thanks