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.

10 Responses to “Making a Simple PHP Captcha that Works”

  1. Sebastián García Rojas Says:

    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.

  2. linus Says:

    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.

  3. alex Says:

    hi nice site.

  4. Jonathan Schultz Says:

    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

  5. Jonathan Schultz Says:

    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

  6. Juan Says:

    phenomenal captcha. Wow I didn’t think it would be so easy!!

  7. Grommit Says:

    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.

  8. Fearghal Says:

    Great code….simple to understand and effective….Cheers!!

  9. Fearghal Says:

    Great code….simple to understand and effective….Cheers!! See it in my blog www dot omegaxiii dot com

  10. sopitikoj Says:

    Hi

    Placed to bookmark!

    Bye

Leave a Reply