Note: This tutorial was originally written 10 years ago. It has now been updated to use new code for generating a random string.
People write code every day to automate a variety of processes. We exploit the fact that computers are a lot faster and more accurate than humans, which lets us simplify a lot of mundane tasks. Unfortunately, these same abilities can be used to program computers to do something malicious like sending spam or guessing passwords. The focus of this tutorial will be on combating spam.
Let's say you have a website with a contact form to make it easy for visitors to contact you. All they have to do is fill out a form and hit the send button to let you know about a problem or request they have. This is an important feature of a public-facing website, but the process of filling out form values can be automated by malicious users to send a lot of spam your way. This type of spamming technique is not limited to contact forms. Bots can also be used to fill your forums with spam posts or comments that link to harmful websites.
One way to solve this problem is to devise a test which can distinguish between bots which are trying to spread spam and people who legitimately want to contact you. This is where CAPTCHAs come in. They generally consist of images with a random combination of five or six letters written on a colored background. The idea is that a human will be able to read the text inside the image, but a bot won't. Checking the user-filled CAPTCHA value against the original can help you distinguish bots from humans. CAPTCHA stands for "completely automated public Turing test to tell computers and humans apart".
In this tutorial, we will learn how to create our own CAPTCHAs and then integrate them with the contact form we created in the tutorial.
Creating the CAPTCHA
We will use the PHP GD library to create our CAPTCHA. You can learn more about writing text and drawing shapes with GD in one of my earlier tutorials. We will also have to write a little bit of code to create our random string to be written on the image that's created. Another tutorial, titled Generating Random Alphanumeric Strings in PHP, can help us in this regard.
Generate a Random String
All the code from this section will go in the captcha.php file. Let's begin by writing the function to create the random string.
<?php $permitted_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; function generate_string($input, $strength = 5) { $input_length = strlen($input); $random_string = ''; for($i = 0; $i < $strength; $i++) { $random_character = $input[mt_rand(0, $input_length - 1)]; $random_string .= $random_character; } return $random_string; } $string_length = 6; $captcha_string = generate_string($permitted_chars, $string_length); ?>
The $permitted_chars
variable stores all the characters that we want to use to generate our CAPTCHA string. We are only using capital letters in the English alphabet to avoid any confusion that might arise due to letters or numbers that might look alike. You can use any set of characters that you like to increase or decrease the difficulty of the CAPTCHA.
Our function creates a five-letter string by default, but you can change that value by passing a different parameter to the generate_string()
function.
Generate a Cryptographically Secure Random String
You can use cryptographically secure functions to generate random strings and make the CAPTCHA harder to guess.
In this particular case, we can use the random_int()
function in place of mt_rand()
. It accepts the same two parameters but generates cryptographically secure random numbers. Here is our modified code for generating the random strings.
<?php $permitted_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; function secure_generate_string($input, $strength = 5, $secure = true) { $input_length = strlen($input); $random_string = ''; for($i = 0; $i < $strength; $i++) { if($secure) { $random_character = $input[random_int(0, $input_length - 1)]; } else { $random_character = $input[mt_rand(0, $input_length - 1)]; } $random_string .= $random_character; } return $random_string; } $string_length = 6; $captcha_string = secure_generate_string($permitted_chars, $string_length); ?>
The only change we make here is the use of a third parameter in our generate_string()
function. This third parameter called $secure
is set to true
by default. Later in the code, we check if $secure
is set to true
and then use random_int()
to generate the string.
Modifying our function this way allows us to make the output of all our previous calls of the function cryptographically secure without making any changes to old code. It also allows us to generate random strings which are not cryptographically secure by explicitly setting the third parameter to false
in any future calls.
Render the CAPTCHA Background
Once we have our random string, it's time to write the code to create the background of the CAPTCHA image. The image will be 200 x 50 pixels in size and will use five different colors for the background.
<?php $image = imagecreatetruecolor(200, 50); imageantialias($image, true); $colors = []; $red = rand(125, 175); $green = rand(125, 175); $blue = rand(125, 175); for($i = 0; $i < 5; $i++) { $colors[] = imagecolorallocate($image, $red - 20*$i, $green - 20*$i, $blue - 20*$i); } imagefill($image, 0, 0, $colors[0]); for($i = 0; $i < 10; $i++) { imagesetthickness($image, rand(2, 10)); $rect_color = $colors[rand(1, 4)]; imagerectangle($image, rand(-10, 190), rand(-10, 10), rand(-10, 190), rand(40, 60), $rect_color); } ?>
We begin with random values for the variables $red
, $green
, and $blue
. These values determine the final color of the image background. After that, we run a for
loop to create progressively darker shades of the original color. These colors are stored in an array. The lightest color is the first element of our $colors
array, and the darkest color is the last element. The lightest color is used to fill the whole background of the image.
In the next step, we use a for
loop to draw rectangles at random locations on our original image. The thickness of the rectangles varies between 2 and 10, while the color is chosen randomly from the last four values of our $colors
array.
Drawing all these rectangles adds more colors to the background, making it a little harder to distinguish the foreground of the CAPTCHA string from the background of the image.
Your CAPTCHA background should now look similar to the following image.
Render the CAPTCHA String
For the final step, we just have to draw the CAPTCHA string on our background. The color, y-coordinate, and rotation of individual letters is determined randomly to make the CAPTCHA string harder to read.
<?php $black = imagecolorallocate($image, 0, 0, 0); $white = imagecolorallocate($image, 255, 255, 255); $textcolors = [$black, $white]; $fonts = [dirname(__FILE__).'\fonts\Acme.ttf', dirname(__FILE__).'\fonts\Ubuntu.ttf', dirname(__FILE__).'\fonts\Merriweather.ttf', dirname(__FILE__).'\fonts\PlayfairDisplay.ttf']; $string_length = 6; $captcha_string = secure_generate_string($permitted_chars, $string_length); for($i = 0; $i < $string_length; $i++) { $letter_space = 170/$string_length; $initial = 15; imagettftext($image, 20, rand(-15, 15), $initial + $i*$letter_space, rand(20, 40), $textcolors[rand(0, 1)], $fonts[array_rand($fonts)], $captcha_string[$i]); } header('Content-type: image/png'); imagepng($image); imagedestroy($image); ?>
As you can see, I'm using some fonts I downloaded from Google to get some variation in the characters. There is a padding of 15 pixels on both sides of the image. The leftover space—170 pixels—is divided equally among all the CAPTCHA letters.
After rendering the text string above the background, your result should look similar to the image below. The characters will be different, but they should be slightly rotated and a mix of black and white.
Adding the CAPTCHA to Our Contact Form
Now that we have created our CAPTCHA, it's time to add it to our contact form. We will use the contact form from my previous tutorial on how to create a PHP contact form and add the CAPTCHA just above the Send Message button.
We will be using sessions to store the CAPTCHA text and then validate the text entered by website visitors. Here is the complete code of our captcha.php file:
<?php session_start(); $permitted_chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; function generate_string($input, $strength = 10) { $input_length = strlen($input); $random_string = ''; for($i = 0; $i < $strength; $i++) { $random_character = $input[mt_rand(0, $input_length - 1)]; $random_string .= $random_character; } return $random_string; } $image = imagecreatetruecolor(200, 50); imageantialias($image, true); $colors = []; $red = rand(125, 175); $green = rand(125, 175); $blue = rand(125, 175); for($i = 0; $i < 5; $i++) { $colors[] = imagecolorallocate($image, $red - 20*$i, $green - 20*$i, $blue - 20*$i); } imagefill($image, 0, 0, $colors[0]); for($i = 0; $i < 10; $i++) { imagesetthickness($image, rand(2, 10)); $line_color = $colors[rand(1, 4)]; imagerectangle($image, rand(-10, 190), rand(-10, 10), rand(-10, 190), rand(40, 60), $line_color); } $black = imagecolorallocate($image, 0, 0, 0); $white = imagecolorallocate($image, 255, 255, 255); $textcolors = [$black, $white]; $fonts = [dirname(__FILE__).'\fonts\Acme.ttf', dirname(__FILE__).'\fonts\Ubuntu.ttf', dirname(__FILE__).'\fonts\Merriweather.ttf', dirname(__FILE__).'\fonts\PlayfairDisplay.ttf']; $string_length = 6; $captcha_string = generate_string($permitted_chars, $string_length); $_SESSION['captcha_text'] = $captcha_string; for($i = 0; $i < $string_length; $i++) { $letter_space = 170/$string_length; $initial = 15; imagettftext($image, 24, rand(-15, 15), $initial + $i*$letter_space, rand(25, 45), $textcolors[rand(0, 1)], $fonts[array_rand($fonts)], $captcha_string[$i]); } header('Content-type: image/png'); imagepng($image); imagedestroy($image); ?>
The fonts that you want to use will go into the fonts directory. Now, you simply have to add the following HTML code above the Send Message button from our previous tutorial on creating a contact form in HTML and PHP.
<div class="elem-group"> <label for="captcha">Please Enter the Captcha Text</label> <img src="captcha.php" alt="CAPTCHA" class="captcha-image"><i class="fas fa-redo refresh-captcha"></i> <br> <input type="text" id="captcha" name="captcha_challenge" pattern="[A-Z]{6}"> </div>
Sometimes, the CAPTCHA text will be hard to read even for humans. In these situations, we want them to be able to ask for a new CAPTCHA in a user-friendly manner. The redo icon above helps us do exactly that. All you have to do is add the JavaScript below on the same page as the HTML for the contact form.
var refreshButton = document.querySelector(".refresh-captcha"); refreshButton.onclick = function() { document.querySelector(".captcha-image").src = 'captcha.php?' + Date.now(); }
After integrating the CAPTCHA in the form and adding a refresh button, you should get a form that looks like the image below.
The final step in our integration of the CAPTCHA we created with the contact form involves checking the CAPTCHA value input by users when filling out the form and matching it with the value stored in the session. Update the contact.php file from the previous tutorial to have the following code.
<?php session_start(); if($_POST) { $visitor_name = ""; $visitor_email = ""; $email_title = ""; $concerned_department = ""; $visitor_message = ""; if(isset($_POST['captcha_challenge']) && $_POST['captcha_challenge'] == $_SESSION['captcha_text']) { if(isset($_POST['visitor_name'])) { $visitor_name = filter_var($_POST['visitor_name'], FILTER_SANITIZE_STRING); } if(isset($_POST['visitor_email'])) { $visitor_email = str_replace(array("\r", "\n", "%0a", "%0d"), '', $_POST['visitor_email']); $visitor_email = filter_var($visitor_email, FILTER_VALIDATE_EMAIL); } if(isset($_POST['email_title'])) { $email_title = filter_var($_POST['email_title'], FILTER_SANITIZE_STRING); } if(isset($_POST['concerned_department'])) { $concerned_department = filter_var($_POST['concerned_department'], FILTER_SANITIZE_STRING); } if(isset($_POST['visitor_message'])) { $visitor_message = htmlspecialchars($_POST['visitor_message']); } if($concerned_department == "billing") { $recipient = "billing@domain.com"; } else if($concerned_department == "marketing") { $recipient = "marketing@domain.com"; } else if($concerned_department == "technical support") { $recipient = "tech.support@domain.com"; } else { $recipient = "contact@domain.com"; } $headers = 'MIME-Version: 1.0' . "\r\n" .'Content-type: text/html; charset=utf-8' . "\r\n" .'From: ' . $visitor_email . "\r\n"; if(mail($recipient, $email_title, $visitor_message, $headers)) { echo '<p>Thank you for contacting us. You will get a reply within 24 hours.</p>'; } else { echo '<p>We are sorry but the email did not go through.</p>'; } } else { echo '<p>You entered an incorrect Captcha.</p>'; } } else { echo '<p>Something went wrong</p>'; } ?>
We updated this file to first check if the CAPTCHA value stored in the session is the same as the value input by the user. If they are different, we simply tell the visitors that they entered an incorrect CAPTCHA. You can handle the situation differently based on what your project needs.
Final Thoughts
In this tutorial, we created our own CAPTCHA in PHP from scratch and integrated it with a PHP contact form we built in one of our earlier tutorials. We also made the CAPTCHA more user-friendly by adding a refresh button so that users get a new string with a new background in case the previous one was unreadable.
You can also use the logic from this tutorial to create a CAPTCHA that relies on solving basic mathematical equations like addition and subtraction.
If you want to add a CAPTCHA to your website, you should check out some of the form and CAPTCHA plugins available from CodeCanyon. Some of these have CAPTCHA and many other features like a file uploader built in.
-
PHP18 Best Contact Form PHP Scripts for 2020
-
PHPComparing the 5 Best PHP Form Builders (And 4 Free Scripts)
-
PHP19 PHP Login and Registration Forms to Download
If you have any questions or suggestions, feel free to let me know in the comments. You should also take a look at this list of the best PHP contact forms.
No comments:
Post a Comment