Archive for February, 2007

Making a Simple PHP Captcha that Works

Wednesday, February 7th, 2007

… 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.