Build Your Own Captcha and Contact Form

Build Your Own Captcha and Contact Form

Ever get hit with spam through the contact form on your personal site? Well, here is a short tutorial on how to build a custom captcha to keep the bad guys out.

Step 1: captcha.php and the Session

First we need to build a new PHP page and save it as captcha.php. Then, in out new script, open a server session by using the session_start() command. Also, code an empty variable named “string”. We will use this variable later to hold our randomly generated captcha text.

<?php

session_start();

$string = '';

?>

Step 2: Random String

Next, we need to write a for loop that will generate a random string. We will then take this random string and set it in a session variable called “random_code“.

<?php

session_start();

$string = '';

for ($i = 0; $i < 5; $i++) {
	$string .= chr(rand(97, 122));
}

$_SESSION['random_code'] = $string;

This for loop, you'll notice, adds a lower case ascii character, using the chr() function, to our $string variable on every pass. My example generates 5 characters, but you can adjust that number by changing "$i < 5" in the loop statement to something more custom, like "$i < 7". Once your loop is complete, make sure you define your session variable.

Step 3: Storage Folder and Colors

This is where the tutorial gets a little more complicated. Next we need to define a storage folder for the font we are going to use, build the base captcha image, and define the colors we'll use to fill our image. This is all simple code, but they're functions that don't get used often by developers.

$dir = 'fonts/';

$image = imagecreatetruecolor(170, 60);
$black = imagecolorallocate($image, 0, 0, 0);
$color = imagecolorallocate($image, 200, 100, 90); // red
$white = imagecolorallocate($image, 255, 255, 255);

Initially, I'm just defining the folder where my fonts are stored in the $dir variable. The $image variable, where we use the imagecreatetruecolor() function is the money spot. This is where the base captcha image is built using PHP. The function imagecreatetruecolor() returns an image identifier representing a black image of the specified size. As you can see, I'm making my image 170px wide by 60px tall.

Finally, in this step, I define some colors we can use in our final image. The numbers passed to the imagecolorallocate() function are RGB values.

Step 4: Building the Image

Next, we're going to fill our image with a white rectangle, which will act as the image background, and then add our random text string to the image.

imagefilledrectangle($image,0,0,200,100,$white);
imagettftext($image, 30, 0, 10, 40, $alt, $dir."arial.ttf", $_SESSION['rand_code']);

The imagefilledrectangle() function draws a rectangle in the specified image. The four numbers passed in the function represent coordinates for the corners of the rectangle. Make sure the rectangle you draw here is larger than the base image. You'll notice my rectangle is 200px wide and 100px tall.

The imagettftext() lets us add text to an image using True Type fonts. And you'll see that this function that can handle quite a few parameters. I'd like to highlight all of the different parameters in this function, but you'll only need to remember a few.

imagettftext($image, $font_size, $angle, $x, $y, $color ,$font_file ,$text);

Once you compare my example to the code immediately above, you'll see that the values passed to the imagettftext() function are easy to understand. First is the image, then font size, angle of the text, the x and y coordinates of the text (starting with the top left corner), text color, the location of the font file, and finally the text (our random string).

Step 5: Image Final

Next, with our script, we need to tell our browser what type of image we are using, with a header() function, and build the final image. These functions are so straight-forward, not much can be explained about them. Also, don't forget to close your PHP script.

header("Content-type: image/png");
imagepng($image);

?>

Once previewed in a browser, you script should generate a png image that contains some text. If you receive errors, make sure your script can link to your .ttf font file, and that you have created the empty $string variable from earlier in the tutorial.

This is what the final code for your captcha.php page should look like:

<?php
session_start();

$string = '';

for ($i = 0; $i < 5; $i++) {
	// this numbers refer to numbers of the ascii table (lower case)
	$string .= chr(rand(97, 122));
}

$_SESSION['rand_code'] = $string;

$dir = 'fonts/';

$image = imagecreatetruecolor(170, 60);
$black = imagecolorallocate($image, 0, 0, 0);
$color = imagecolorallocate($image, 200, 100, 90); // red
$white = imagecolorallocate($image, 255, 255, 255);

imagefilledrectangle($image,0,0,399,99,$white);
imagettftext ($image, 30, 0, 10, 40, $color, $dir."arial.ttf", $_SESSION['random_code']);

header("Content-type: image/png");
imagepng($image);
?>

Step 6: contact.php

Build a new PHP contact page and save it as contact.php. This page will contain our contact form that will validate using our captcha.

Step 7: HTML & CSS

Let's add an HTML form to our contact.php page. Pay particular attention to the image source we use for the random text.

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data">
	<p><input type="text" name="name" /></p>
	<p><input type="text" name="email" /></p>
	<p><textarea name="message"></textarea></p>
	<img src="captcha.php"/>
	<p><input type="text" name="code" /></p>
	<p><input type="reset" name="submit" value="Send" /></p>
</form>

You can also add the following bit of CSS to your page to make it look better than default.

<style type="text/css">
form {
	margin:0;
	padding:0;
}
input {
	padding:2px;
	width:200px;
}
textarea {
	padding:2px;
	width:200px;
	height:100px;
}
.button {
	width:60px;
}
p {
	margin:0 0 5px 0;
	padding:0;
}
.error {
	color:#FF0000;
	margin:0 0 10px 0;
}
.accept {
	color:#339966;
	margin:0 0 10px 0;
}
</style>

Step 8: Validate with PHP

Now that our form is built and we have out captcha image displaying, all we need to do now is validate our form, being sure to include some validation rules for out captcha.

Below is what the final validation PHP should look like. Two important features required for this validation process are session_start(); and the if($_POST['code'] == $_SESSION['rand_code']) elements. These allow us to access out session variable and check it against the text someone writes in the code field of our form. Without these, our captcha would be useless.

<?php
session_start();

if(isset($_POST['submit'])) {

	if(!empty($_POST['name']) && !empty($_POST['email']) && !empty($_POST['message']) && !empty($_POST['code'])) {

		if($_POST['code'] == $_SESSION['rand_code']) {

			// send email
			$accept = "Thank you for contacting me.";

		} else {

			$error = "Please verify that you typed in the correct code.";

		}

	} else {

		$error = "Please fill out the entire form.";

	}

}
?>

If you know much about PHP, the rest of this validation should be easy to understand. We are essentially looking to make sure none of our form fields are empty. If they are empty, errors are thrown to make sure our user inputs information. You'll also notice I am not validating whether the email is well formed, which is something your should should include. Email validation is done using regular expressions.

Here is what your final contact.php file should look like. I am including the CSS, which you may want to drop in its own CSS file.

<?php

session_start();

if(isset($_POST['submit'])) {

	if(!empty($_POST['name']) && !empty($_POST['email']) && !empty($_POST['message']) && !empty($_POST['code'])) {

		if($_POST['code'] == $_SESSION['rand_code']) {

			// send email
			$accept = "Thank you for contacting me.";

		} else {

			$error = "Please verify that you typed in the correct code.";

		}

	} else {

		$error = "Please fill out the entire form.";

	}

}

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Contact Us</title>
<style type="text/css">
form {
	margin:0;
	padding:0;
}
input {
	padding:2px;
	width:200px;
}
textarea {
	padding:2px;
	width:200px;
	height:100px;
}
.button {
	width:60px;
}
p {
	margin:0 0 5px 0;
	padding:0;
}
.error {
	color:#FF0000;
	margin:0 0 10px 0;
}
.accept {
	color:#339966;
	margin:0 0 10px 0;
}
</style>
</head>

<body>

<?php if(!empty($error)) echo '<div class="error">'.$error.'</div>'; ?>
<?php if(!empty($accept)) echo '<div class="accept">'.$accept.'</div>'; ?>

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data">
	<p><input type="text" name="name" /> Name</p>
	<p><input type="text" name="email" /> Email</p>
	<p><textarea name="message"></textarea></p>
	<img src="captcha.php"/>
	<p><input type="text" name="code" /> Are you human?</p>
   <p><input type="submit" name="submit" value="Send" class="button" /></p>
</form>

</body>
</html>

You're Done

Once your validation is working, your captcha contact form should be working great. What are your thoughts? Thanks so much for reading.


Add Comment

Discussion 147 Comments

Comment Page 1 of 31 2 3
  1. Allen says:

    That’s funny, I was just looking for this at this second

    • Shuuun says:

      Then better search again!
      Every “Form hack” bot on this planet can hack this captcha o0
      Same as “what the font” you can just give a screenshot to the bot and it will compare the image with letters and voila: you have been hacked..

  2. stealth01 says:

    Hey Jeffrey, quick question: Do you have a time of the day when you post articles to the website? I often end up checking your website too many times too often everyday before I see a new article. (Tells you how much I enjoy this site. :-)
    ————————-
    – Another great tut!!
    ——————-

  3. stealth01 says:

    “Ever get hit with spam through the contact form on your personal site?”

    –understatement of the century !

    • Philo says:

      lol :P
      I once had a blog system without captcha few years ago xD
      Received like 1500 spam entries every day :P

      Nice tutorial for starters :)

  4. mr_chopper says:

    Never heard of RSS..?

  5. Sergei says:

    You should use ReCaptcha and help digitize books. It works awesome and its extremely simple to implement.

  6. ed says:

    Doesn’t look too hard to get past? At the very least some other font than the world’s most common one should probably be used…

  7. geekbay says:

    Hi,

    some things about this code, there is an error in captcha.php on line 21
    it’s not $_SESSION['random_code'] but $_SESSION['rand_code']

    there is also a security problem with this captcha

    let say i saw the image and the code displayed is abcde so now i see that $_SESSION['rand_code'] = ‘abcde’ so if i don’t display again the image i can submit the form another time and another time without changing the code but i can the change the email and message. In the case of a contact form it’s not critical, but if it was a send to friend alike form, i can literaly spam.

    (sorry for the bad english :)

  8. Harry says:

    Great tut!

    The only problem I could see is that blind users might need to hear the text and that’s where reCaptcha has the upper edge.

    • Web020 says:

      Do you hear yourself? Why would a blind person visit a website???????

      • James W Lane says:

        Blind people surfing the internet every day, that’s one reasons designers should account accessibility also into there websites. I think it shows your level of design experience if you don’t know or understand simple concepts like accessibility.

        @Harry – I totally agree

        @Zachary Vineyard – Great tut, but it would of be nicer to if it went to more advanced techniques cause I would have to agree reCaptcha is a better more accessible way of stopping spam.

      • Web010 says:

        I’m not a web designer, I’m a programmer.

      • a programmer without a job with that attitude.

      • @Web020 – Blind users use the internet just like any other person does. They use screen readers. Ever heard of them?

      • Fynn says:

        Concerning accessibility, you should read this article about labelling form elements, making use of fieldsets and styling them correctly in CSS:

        http://www.sitepoint.com/article/fancy-form-design-css/1/

        And accessibility does not only accounts for blind people, there are a lot of people with less good eyesight. Making your website accesible for this target group does not only increase you site’s user friendliness, it can also improve your profit!

  9. Jesse says:

    I’d like to see this built out with a hidden form field that should remain empty but that tempts ‘bots into submitting a value. If you have little to no knowledge of PHP but still need a secure contact form, I’d recommend Green Beast’s Secure and Accessible Form Script http://green-beast.com/gbcf-v3/ Nice tut!

  10. Timothy says:

    A bit of a start, but not that good. Captchas like this are easily detectable by bots. Bots normally crack captchas by splitting each letter apart and analyzing them. This defeats the purpose of the captcha.

    To make a secure captcha you should make the letters mesh as close together as possible while remaining readable. Like Geekbay said you should not store the plain-text in a session. Instead, you should hash it, and then compare the hashed user-inputed value to the original hash. And like Jesse said you should also have a hidden input field that tempts bots to enter a value.

    Here’s a good article on this:
    http://pushingbuttons.net/?post=103
    http://pushingbuttons.net/?post=104

  11. Josh says:

    The captcha image does not work for me. Maybe it’s a font thing? Can someone explain what is meant by…

    “if you receive errors, make sure your script can link to your .ttf font file”

    Is this why it does not work.

    - PS. I’m using a Mac, does that matter?

    • Raymond Lopez says:

      upload a true type font(.ttf) to the “font” folder:

      ex. arial.ttf

      and write the exact name of the font with the extension on the captcha.php file

  12. Ian says:

    Not a bad tutorial… only problem is the text in the image this script creates is so simple to OCR that it won’t stop most bots. I recommend checking the captcha scripts that comes with CodeIgniter. Towards the end of the script you’ll see the code that distorts the text… although even this won’t trick the really high end bots. Check out the script on their public SVN: http://dev.ellislab.com/svn/CodeIgniter/trunk/system/plugins/captcha_pi.php

  13. nice tut for starters

  14. Crysfel says:

    Good tutorial! also you can add some noise to the image :D

  15. this is another cool and useful tuts again thanks.

  16. Ron says:

    In the imagettftext you use $alt instead of $color, which causes an error. Your full code later on is correct though

  17. Nathan says:

    This is very useful. I hate some of the downloadable ones because you can’t customize them enough. This way I can do it myself!

  18. kevinsturf says:

    great tut man, nice, ill sure be using this.

  19. bballbackus says:

    Although I do enjoy this tutorial I can’t help but wonder that this wouldn’t prevent most bots. Doesn’t the text need to be distorted and skewed enough for the bot to not be able to split and decipher it?

  20. Two points really. Firstly don’t use action="" otherwise you are vulnerable to XSS attacks. Why not use action="" ?

    Secondly as geekbay pointed out, if the image isn’t reloaded the captcha text isn’t changed. I would put
    $string = '';
    for ($i = 0; $i < 5; $i++) {
    // this numbers refer to numbers of the ascii table (lower case)
    $string .= chr(rand(97, 122));
    }
    $_SESSION['rand_code'] = $string;

    at the bottom of your second script so every time it is submitted the captcha is redefined. At the top of the first script use
    $string = isset($_SESSION['random_code']) ? $_SESSION['random_code'] : '';

  21. Sorry that last comment was completely mashed up by wordpress it should say

    Firstly don’t use action=”<?php echo $_SERVER['PHP_SELF']; ?>” otherwise you are vulnerable to XSS attacks. Why not use action=”" ?

  22. And that last comment still isn’t perfect :( Jeffrey why aren’t code tags allowed in comments? Surely that is obvious on a site like Nettuts?

  23. Very cool tutorial. One thing I would like to see is the code, after successful submission, take you to another URL, and have that be set somewhere easy to access. This is an awesome tutorial, and I will use it on my next HTML site.

    Thank you!

  24. James says:

    Very useful! Thanks

  25. Ben Kuker says:

    I’ve always wondered if there isn’t a better way to handle spam than a CAPTCHA, but so far, I haven’t found a decent method other than honeypots of some sort of bayesian spam filters. As CAPTCHA’s go, this one will only prevent the simplest of spam bots because the text isn’t distorted, it’s all on the same line, there are no lines through it, all the letters are the same color, and there is no noise. Anything with image recognition will go through this pretty easily.

    As others have mentioned, reCaptcha is a good option, as is Akismet, a spam filtering service.

  26. It is a great tut fallen from heaven for me, I have searched something like this for my blog.

  27. Moksha says:

    its always PHP, i think i must also learn PHP as there are not much ASP.net here, or search for a site where i can find quality tutorial like here.

    thanks for it,

  28. Kasdan says:

    I tried this out and my image shows different characters than $_session['random_code'] and therefore it fails everytime. Any ideas?

  29. James Read says:

    Having just coded a simple message board for my personal site and disabled it less than 24 hours later due to spam this is exactly what i need right now. Great tutorial. I will also include the hidden field method as mentioned above when i implement a captcha to my site.

    I’ve been thinking about other methods and wondered if asking a simple question that only humans would understand would provide further security? ie. if simple questions such as ‘What colour is the sky?’, ‘How many hours in a day?’ etc and their answers were stored in a database, surely this would fool a bot?

  30. Juan Pina says:

    Thanks for this great tut. This is the type of stuff that makes me want to keep expanding my knowledge on PHP. Keep up the great job

  31. stk says:

    CAPTCHA has already been defeated and is an extra hurdle for visitors. It shouldn’t be used and there are many better spam-detection methods. Don’t waste your time.

  32. MightyUhu says:

    as a paranoid supersecurityguru this is way to easy to decode for a non human ocr tool

    see this: http://caca.zoy.org/wiki/PWNtcha

  33. Web010 says:

    Highly insecure !!!!!!!!!!

    Sorry for this comment, but that’s the truth.

    1) Using sessions for a “secret code” without any encryption.
    2) The image does look nice and clean, but bots can easily get the code from it.

  34. Courtney says:

    this has mostly made sense to me, however how do you tell it where to submit the form to?

  35. Despite a few people pointing out a few niggles with this TUT, the basics for getting your own captcha to work and the coding behind is great.

    Good job Zachary.

  36. Haious says:

    Grate, Tutorial

    Thanks for sharing

  37. petr says:

    this is the best SITE I’ve ever seen

    SPB. from Russia

  38. A very poor system, a simple bot can bypass it… Maybe with some “noise” on the image the system could be better…

  39. Seed says:

    I have this error when I’m testing the captcha.php file: Fatal error: Call to undefined function imagecreatetruecolor() in /Library/WebServer/Documents/e-car/e-car/captcha.php on line 15

  40. oelewapperke says:

    Heh, cracking this captcha is so easy it barely needs mentioning at all :

    > convert 2.jpg -crop 150×50+10+90 2p.png
    > gocr 2p.png | sed -e ‘s/ //g’ | sed -e ‘s/I/l/g’
    mfelr

    Hmmm captcha cracked. There isn’t even the need to train the recognition engine, no need to get a neural network up, nothing.

    I wouldn’t really bother fixing it though. Real captcha cracking tools have long since left “normal”* human 2d recognition skills behind.

    * normal as in what you could expect from a random visitor. Of course you could easily ask hard domain-specific questions, or challenges, but there’d be lots of humans not capable of responding to your captcha too. In fact, several people in my family are already complaining captchas are getting too hard.

    I doubt you’ll get many years of peace and quiet with domain-specific questions either, though. Markov chains can easily give “human” answers to textual questions, or stuff like calculation challenges, or “Marilyn ….” “… Bush” (fill in the blank) type questions, no matter how general you make them.

  41. Good tutorial for beginners. Thank you.

  42. Martyn Web says:

    Great little tutorial, I have quite a few projects at the moment which could do with something like this. I’ve never created a captcha before so nice tutorial for the beginner.

  43. Patrick says:

    yeah i needed this two days ago… glad its here!

  44. Diego SA says:

    This one I have to try! These captchas drive me angry sometimes but it doesn’t matter since security comes first. And I found it surprising ’cause it’s easier than I thought. And it’ll be interesting in some websites I’m doing. Thanks!

  45. matt lopez says:

    how can i set it up so it sends it to my e-mail?
    where is this sending?

  46. 46Bit says:

    Using rand() is an even worse idea than mt_rand(). Common, predictable random functions like those two can be cracked. For a small scale CAPTCHA (like this one, especially with the easy to crack font), not a problem, but it’s still not a very good idea to just use rand() in a tutorial for beginners.

    Then again, you fall into the same trap almost every tutorial site on the internet – there’s little in the way of advice on this, as far as my quick few google searches could tell and I could remember. Most security info obsesses over XSS and CSRF (and SQL Injection) and skims everything else.

    If anyone’s interested in this, see http://zly.me/c760/ for some information on the matter.

Comment Page 1 of 31 2 3

Add a Comment

To add a code snippet to your comment, please wrap your code like so: <pre name="code" class="html">YOUR CODE</pre>. You can replace the class name with "js," "css," "sql," or "php." If there are any "<" or ">" within your code, please search and replace them with: &lt; and &gt; respectively.