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.


Tags: security
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://www.ill-fx-designs.com Allen

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

    • Shuuun

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

      • http://blog.insicdesigns.com insic

        you can add distortions like lines and can twist the character by adding values to your angle variable to make it harder to read even to humans lol.

      • http://www.mathias.li Mathias

        Depends on what font you use, there are quite many almost unreadeable fonts out there. But if you use arial a bot can read it as clear as a human.

      • http://www.think-new.com Adam

        I hate the captchas that are so hard to read you get it wrong multiple times

      • Austin

        @Adam

        Gosh me too, there has got to be a better way.

  • http://nasibg.info Nasi

    Good

    • Snookerman

      Terrible, might as well go without the captcha, it wouldn’t make any difference.

    • http://threepixeldrift.co.cc Taylor Satula

      As snooker said, this should be edited to have things done to the text

  • stealth01

    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!!
    ——————-

    • Gonzalo

      Use a RSS reader. That way you’ll be alerted as soon as an article is posted.

      http://net.tutsplus.com/about/rss-feeds/

      • http://www.jeff-way.com Jeffrey Way

        I’ve been experimenting with different times. In this last month, I post new articles around 11:00 AM (central standard time).

    • Vlas

      or twitter XD

  • stealth01

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

    –understatement of the century !

    • http://www.philohermans.nl Philo

      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 :)

  • mr_chopper

    Never heard of RSS..?

  • http://sergei.me Sergei

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

    • http://threepixeldrift.co.cc Taylor Satula

      I forgot about that, That is a must have, not too easy to break either

  • ed

    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…

  • http://i3m.co.uk Symon

    Good intro.

  • http://twitter.com/geekbay geekbay

    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 :)

    • http://www.bradenkeith.com Braden Keith

      A session_destroy() could be an easy solution for that. Have it at the bottom of processing the form, after it’s checked if it’s correct.

  • http://harrisonbachrach.com/ Harry

    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

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

      • http://www.jameswlane.com James W Lane

        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

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

      • http://www.stephen-ainsworth.co.uk Stephen Ainsworth

        a programmer without a job with that attitude.

      • http://twitter.com/VizionThree Silver Firefly

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

      • Fynn

        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!

  • http://www.racinewebworks.com Jesse

    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!

  • http://www.twitter.com/timothymarshall Timothy

    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

  • http://joshuabader.com Josh

    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

      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

  • Ian

    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

  • http://www.imblog.info Muhammad Adnan

    nice tut for starters

  • http://www.quizzpot.com Crysfel

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

    • Raymond Lopez

      how?

  • http://myfacefriends.com Myfacefriends

    this is another cool and useful tuts again thanks.

  • Ron

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

  • http://www.cetan.ca Nathan

    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!

  • kevinsturf

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

  • http://backusdesign.com bballbackus

    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?

  • http://instantsolve.net Thomas Milburn

    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'] : '';

    • http://vasili.duove.com/ Vasili

      Or you can just use session_destroy() when validating the form..

  • http://instantsolve.net Thomas Milburn

    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=”" ?

  • http://instantsolve.net Thomas Milburn

    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?

    • http://teamtutorials.com john

      use & tags…… &lt ; (with no space inbetween the ;) = <

      • http://teamtutorials.com john

        ….and my comment was converted to a smile…

    • http://11heavens.com/ Caroline Schnapp

      Please net.tuts figure out how people can post code easily. Like wrapping it between tags and voila. How hard can it be?

      I wish you were using Drupal.

      • http://11heavens.com/ Caroline Schnapp

        Where is the formatting guide for posting comments, anyway?

        Truly lacking, what a shame.

  • http://www.benniemosher.com Bennie Mosher

    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!

  • http://findsomeonesemailaddress.co.cc/ James

    Very useful! Thanks

  • Ben Kuker

    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.

  • http://labs.dariux.com Dario Gutierrez

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

  • http://mokshasolutions.com Moksha

    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,

    • http://www.goingson.be Rob

      no no

      definitely go with the PHP.

    • Guest

      Well buddy you should try asp.net official websites

  • http://www.kasdanhall.com Kasdan

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

  • http://www.jsread.com/tvr James Read

    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?

  • http://www.gottio.com gotti

    WoW!

  • Juan Pina

    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

  • http://randsco.com/index.php/2007/07/04/may_and_june_spam_stats stk

    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.

  • MightyUhu

    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

  • Web010

    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.

  • Courtney

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

    • Web010

      <form action=”"…

      This means that the form will be processed to the same page. But it’s vulnerable i prefer using the full file name instead of $_SERVER['PHP_SELF'].

      • Web010

        action=”&lt?php echo $_SERVER['PHP_SELF'] ?&gt”

      • Web010

        Still not good, i hope you understand what i mean.

  • http://www.wpdesigner.net iwpdesign

    very useful

  • http://www.nouveller.com/ Benjamin Reid

    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.

  • http://www.haious.org Haious

    Grate, Tutorial

    Thanks for sharing

  • petr

    this is the best SITE I’ve ever seen

    SPB. from Russia

  • http://joaopedropereira.com/ João Pedro Pereira

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

  • Seed

    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

    • Ian

      You need the GD or GD2 libraries for this to work. My guess would be that your host doesn’t have them installed.

      • Seed

        I’m running mac os x leopard, how to install GD libraries to it?

  • oelewapperke

    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.

  • http://freewarematter.blogspot.com FreewareMatter

    Good tutorial for beginners. Thank you.

  • http://www.crearedesign.co.uk Martyn Web

    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.

  • Patrick

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

  • http://www.dsaportfolio.com.br Diego SA

    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!

  • matt lopez

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

  • http://zly.me 46Bit

    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.