Making a Simple PHP Captcha that Works
… Learning is a life long process, if you stop, life stops …
The Problem: I have an email form on my Website, and keep getting these spams “to make my love life better”. While that might be a good intension, I rather not receiving those emails. So, I decided to put a captcha to reduce the number of spams.
I have found bits and pieces on the Web on how to generate a captcha but most of them don’t present a total solution. I will put them all together and hope that this will ease the process of learning.
The Solution: I have this captcha program that generates an image and set a cookie. Then I wrote a html form and a php program that uses this captcha. We first create a php program (captcha.php) that generates the image and set the cookie:
001 <?php
002 function getR($col) {return ( ( $col >> 8 ) >> 8 ) % 256;}
003 function getG($col) {return ( $col >> 8 ) % 256;}
004 function getB($col) {return $col % 256;}
005
006 function create_image() {
007 $bcol = array(0,0,0); // background color
008 $fcol = array(255,228,250); // foreground color
009 $words = array('apple','badge','black'); // words for captcha
010 $fact = 2;
011 $font = 5;
012 $cosrate = rand(10,19);
013 $sinrate = rand(10,18);
014 $charwidth = imagefontwidth($font);
015 $charheight = imagefontheight($font);
016 $pass = $words[rand(0,sizeof($words)-1)];
017 $strlen = strlen($pass);
018 $width = ($strlen + 2) * $charwidth;
019 $height = 2 * $charheight;
020
021 session_start();
022 $_SESSION["pass"] = $pass;
023
024 $im = @imagecreatetruecolor($width, $height)
025 or die("Cannot Initialize new GD image stream");
026 $im2 = imagecreatetruecolor($width*$fact, $height*$fact);
027 $bcol = imagecolorallocate($im, $bcol[0], $bcol[1], $bcol[2]);
028 $fcol = imagecolorallocate($im, $fcol[0], $fcol[1], $fcol[2]);
029
030 imagefill($im, 0, 0, $bcol);
031 imagefill($im2, 0, 0, $bcol);
032 $dotcol = imagecolorallocate($im, (abs(getR($fcol)-getR($bcol)))/2.5,
033 (abs(getG($fcol)-getG($bcol)))/2.5,
034 (abs(getB($fcol)-getB($bcol)))/2.5);
035 $dotcol2 = imagecolorallocate($im, (abs(getR($fcol)-getR($bcol)))/1.5,
036 (abs(getG($fcol)-getG($bcol)))/1.5,
037 (abs(getB($fcol)-getB($bcol)))/3.5);
038 $linecol = imagecolorallocate($im, (abs(getR($fcol)-getR($bcol)))/2.4,
039 (abs(getG($fcol)-getG($bcol)))/2.1,
040 (abs(getB($fcol)-getB($bcol)))/2.5);
041
042 for($i=0; $i<$width; $i=$i+rand(1,5)) {
043 for($j=0; $j<$height; $j=$j+rand(1,5)) {
044 imagesetpixel($im, $i, $j, $dotcol);
045 }
046 }
047
048 imagestring($im, $font, $charwidth, $charheight/2, $pass, $fcol);
049
050 for($j=0; $j<$height*$fact; $j=$j+rand(3,6)) {
051 imageline($im2, 0, $j, $width*$fact, $j, $linecol);
052 }
053
054 for($i=0; $i<$width*$fact; $i=$i+rand(4,9)) {
055 imageline($im2, $i, 0, $i, $height*$fact, $linecol);
056 }
057
058 for($i=0; $i<$width*$fact; $i++) {
059 for($j=0; $j<$height*$fact; $j++) {
060 $x = abs(((cos($i/$cosrate)*5+sin($j/$sinrate*2)*2+$i)/$fact))%$width;
061 $y = abs(((sin($j/$sinrate)*5+cos($i/$cosrate*2)*2+$j)/$fact))%$height;
062 $col = imagecolorat($im, $x, $y);
063 if ($col!=$bcol) imagesetpixel($im2, $i, $j, $col);
064 }
065 }
066
067 for($j=0; $j<$height*$fact; $j=$j+rand(1,5)) {
068 for($i=0; $i<$width*$fact; $i=$i+rand(1,5)) {
069 imagesetpixel($im2, $i, $j, $dotcol2);
070 }
071 }
072
073 imagejpeg($im2);
074 imagedestroy($im);
075 imagedestroy($im2);
076 }
077
078 header("Content-Type: image/jpeg");
079 create_image();
080 ?>
You can try modifying line 7 and 8 for different background and foreground colour to see the effect. Line 9 are the words that are used randomly for generating the captcha (you can add as many as you wish). Then the html form (testcaptcha.html):
001 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
002 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
003 <html xmlns="http://www.w3.org/1999/xhtml">
004 <head>
005 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
006 <title>Captcha Test</title>
007 <style type="text/css">
008 <!--
009 body,td,th {
010 font-family: Arial, Helvetica, sans-serif;
011 }
012 -->
013 </style></head>
014 <body>
015 <form id="captcha_form" name="captcha_form" method="post" action="testcaptcha.php">
016 <img src="captcha.php" /><input name="userpass" type="text" />
017 <input name="submit" type="submit" value="Submit" /></td>
018 </form>
019 </body>
020 </html>
and finally the php program to process the form (testcaptcha.php):
001 <?php
002 session_start();
003 if ($_SESSION["pass"] == $_POST["userpass"]) {
004 echo "You have got the captcha right!";
005 } else {
006 echo "Sorry you failed the captcha";
007 }
008 ?>
This example should get you up and running immediately. Hope you can have fun playing with the example.
March 4th, 2007 at 4:17 am
Hey! This is a great post, thanks! It’s really simple, fast and useful. I think I’ll make a couple of improvements (not now, but I’m thinking of random generated strings, and random colors that still make it readable) and I’ll send them to you, if you are interested, of course. Thanks again! Best Regards,
S.
March 4th, 2007 at 9:57 am
Hi Sebastián,
Thanks for the comment. Of course I am interested in your work in improving the program. You can post it here and if I have time, I will integrate the whole thing so that others can benefit reading it.
April 12th, 2007 at 5:13 am
hi nice site.
May 8th, 2007 at 11:29 pm
I also have been writing a CAPTCHA PHP generator.
Some suggestions:
The hacker could write a simple script to identify the session and post it into the form. This could be stopped by hashing the year, month (after all you don’t want it lasting that long (you could use day as well but that would introduce errors)) with a string specific to the site along with the CAPTCHA code. When submit to the server you then hash the submitted data with the year day and string (in correct order) then compare with the one in the session.
Using a limited array of CAPTCHA’s makes it easier for the attacker to predict the code and so he would have a high approval rate. The best way would be to use a random string of letters (you may want to reduce what letters you use as C and c look similar.)
Also using rand is abit unreliable using mt_rand() is better - I actualy prefer to concatenate several mt_rand’s and hash to produce my random features.
Another way to do it is send a long ID to the user this references a record in a database which also includes the CAPTCHA code.
Hope that helps
Jonathan
May 8th, 2007 at 11:39 pm
There are simple things that would improve the output:
Using imagettftext will let you be able to set angles, this means you could cut up the letters and put them at different angles.
Something like the following code would do this:
$codechop =str_split($captchacode);
$codecount = count($codechop);
$lastletter = 0;
for($i = 0; $i font, $codechop[$i]) or die(’Error creating text’);
$x = $textbox[1] + $margin + $lastletter;
$y = ($height - $textbox[5])/2 + mt_rand(-6, 6);
imagettftext($image, $font_size, mt_rand(-10, 10), $x, $y, $text_color, $this->font , $codechop[$i]) or die(’Error in imagettftext function’);
$lastletter = $lastletter + $textbox[4] + ($textbox[4] / 4);
}
Destroy the session after use, it will stop hackers collecting them and backwards engineering the system(well slow them down).
Send a no-cache header before the image is generated.
Again… hope this helps
Jonathan
May 19th, 2007 at 2:32 pm
phenomenal captcha. Wow I didn’t think it would be so easy!!
May 27th, 2007 at 7:43 pm
Ey, Nice post.
I have been looking for this a long time but within this search time I found an other solution:
http://recaptcha.net/
It’s also captcha but its simpler to implement and it has a speech function.
Not that your script ain’t OK, its just another solution.
June 28th, 2007 at 7:42 pm
Great code….simple to understand and effective….Cheers!!
June 28th, 2007 at 7:43 pm
Great code….simple to understand and effective….Cheers!! See it in my blog www dot omegaxiii dot com
September 8th, 2007 at 12:56 pm
Hi
Placed to bookmark!
Bye