[PHP Tut] Make your own CAPTCHA

mattura

Member
Messages
570
Reaction score
2
Points
18
I haven't seen a tutorial for this yet, so why not make one myself!

First of all, you will need at least the intermediate version of x10's php, so upgrade your account if you haven't already.

It also helps to have a reasonable knowledge of php (or you won't be able to customise this code!)

What is a CAPTCHA?

A CAPTCHA is a Completely Automated Public Turing test to tell Computers and Humans Apart. It gives you something that only a human can read, thereby helping to prevent spam. See here for more details: http://en.wikipedia.org/wiki/Captcha

How do I make one?
Simple! I'll take you through the steps. In the example I'll be creating a png, but it is possible to generate jpg, bmp etc as well.

Step 1 - Create an empty image
PHP:
$imgheight=60;
$imgwidth=400;  //whatever dimensions you need
$img=imagecreatetruecolor($imgwidth,$imgheight);
imagefill($img, 0, 0, 0x99ccff); //fill with light blue
For other background colours, change the '0x99ccff' to the hex value for your favourite colour.

Step 2 - Generate random characters
You may want to skip this step if you know how to get your random characters. There are many ways to do so, but one simple method follows:
PHP:
$str=substr(md5(uniqid(rand(), true)),0,6); //create random token of length 6

Step 3 - Make some random lines etc to confuse Optical Character Recognition
Here we will be using the imageline function. Go look it up if you want to understand it.
First, we will make an array of potential colour to choose from (we'll use this later for the letters as well). Choose them from here, but don't make any similar to the background colour.
PHP:
$colours=array(0x000055,0xff0000,0x009900,0xffcc99,0xff00ff,0x6b8e00);
$cl=count($colours);  //makes processing faster
Then, using a loop to draw about 10 lines (you don't want to overcrowd things), pick random colour values from your array, and also random co-ordinates for each line. I complicated matters by using mostly horizontal-ish and vertical-ish lines, which are most likely to confuse an OCR. Don't worry too much if you don't understand, just copy and paste.
PHP:
for($i=0;$i<10;$i++) {
 $col=$colours[rand(0,$cl)]; //using your array of colour values
 $typ=rand(0,1); //draw either h/v line to confuse OCR
 if ($typ==1) { //horizontal line:
  $x1=rand(0,$imgwidth/2);$x2=rand($imgwidth/2,$imgwidth);
  $y1=rand(0,$imgheight);$y2=rand($y1-$imgheight/5,$y1+$imgheight/5);
 } else {   //vertical line:
  $x1=rand(0,$imgwidth);$x2=rand($x2-$imgwidth/5,$x2+$imgwidth/5);
  $y1=rand(0,$imgheight/2);$y2=rand($imgheight/2,$imgheight);
 }
 imageline($img,$x1,$y1,$x2,$y2,$col); //draw the actual line
}


Step 4 - Write your letters on the image
This makes use of the imagestring function. Again, go look it up if you want to understand it.
PHP:
$let=str_split($str); //convert your string to an array for convenience
$strl=count($let); //count length for later
foreach($let as $key=>$char) {
 $x=$imgwidth/2-$strl*8+$key*16;//*$siz*3+10+rand(1,5);
 $colour=$colours[rand(0,$cl)];
 imagestring($img,5,$x,$imgheight/2-6,$char,$colour);
}

Step 5 - Outputting your image
Ok, finally you are ready to send your image and see what you have produced.
PHP:
header("Content-type: image/png"); //send the appropriate header
return imagepng($img);
That's it! Test your code now and enjoy the result!

But wait - it's really quite small! This is because php's built-in font only has five sizes (1-5), which is what the '5' is in the imagestring line. It doesn't get any bigger!

What can we do about it?
Well, we can use a true type font.
First, you need to find a nice font and save it in the same directory as your php file. If you use windows, you might find them in c:/windows/fonts. Make sure it is *.ttf

Now instead of imagestring, use imagettftext.
I used the following (with the Arial Black font ariblk.ttf saved in my web directory):
PHP:
 $siz=16; //font size, in pt
 $ang=0; //angle of rotation - randomize this for even more fun!
 imagettftext($img, $siz, $ang, $x, $imgheight/2, $colour, "ariblk.ttf", $char);
You will have to change your $x and $y ($imgheight/2) values to reflect the font size

Sample output, using above code:
captcha1.png


Sample output, using close together letters and angles between -10 and 20 (not too negative or 7 looks like 1):
captcha2.png


If additional distortion is required, you can use imagefilter to apply filters to your image after the letters have been written. The output below is generated by using the following code:
captcha3.png

PHP:
imagefilter($img,IMG_FILTER_MEAN_REMOVAL);

Here's one with a different font (TempusITC.ttf). A little harder to read, but a lot harder to detect:
captcha6.png


Bear in mind
The point is to make the image unreadable to a computer, not almost illegible for humans! Many people have great difficulty reading text which is a similar colour to the background, so try to make sure your colour array only contains light colours if your background is dark, or vice versa.
If you eliminate the gap between letters (make them touch each other), clever OCR software has great difficulty in separating the individual letters, this is a huge problem for computers, yet is still pretty readable for humans. Do this if you are worried clever software will try to unleash spam on you. It's unlikely though!
You should offer an alternative for accessibility purposes. Many sites offer an audible CAPTCHA.

Have fun kiddies!
 
Last edited:

marshian

New Member
Messages
526
Reaction score
9
Points
0
If you're making a CAPTCHA, remember you have to know what the user has to enter!
It's nice to have a good CAPTCHA, but if you can't verify the user's input, it's kinda useless ^^
So remember you should store $str somewhere, preferably somewhere where the user can't access it. (No cookies or sessions and if you're using a file, remember you have to make sure the user can't find it.)

You might want to take a look at http://libcaca.zoy.org/wiki/PWNtcha too, it shows a lot of crackable CAPTCHA's and there's some tips on makeing a good one I beleive...
 

mattura

Member
Messages
570
Reaction score
2
Points
18
Well of course you need to store the string.
You can either use a session variable:
PHP:
$_SESSION['captcha']=$str;
---
if ($_POST['user_entry']==$_SESSION['captcha']) {echo "Correct!";}
or you could include the file and use the $str value.
Or just paste the above code in with your input form php file, and again, use the $str value direct.
Don't use a cookie, as the user/spam-bot can read it! But session data is saved on the server, so it's safe.
The tutorial was to create the captcha image, so I kinda didn't bother to write the whole form. You can do that yourself if you're looking into captchas!

A good link above. Thanks

I'd reiterate, that the hardest captchas to solve are those with characters joined up/overlapping
 
Last edited:

DarkDragonLord

New Member
Messages
782
Reaction score
0
Points
0
Great tutorial.

I might try improve it in my Contact form (i still think isn't possible but ya, lets try...)

+rep \o/










btw: u might think, why his form cant? well, its ajax/php one, ajax check the things, then send to php, php process and give ok, ajax go and update div with OK (or error if have any)
 
Last edited:

marshian

New Member
Messages
526
Reaction score
9
Points
0
Well of course you need to store the string.
You can either use a session variable:
PHP:
$_SESSION['captcha']=$str;
---
if ($_POST['user_entry']==$_SESSION['captcha']) {echo "Correct!";}
or you could include the file and use the $str value.
Or just paste the above code in with your input form php file, and again, use the $str value direct.
Don't use a cookie, as the user/spam-bot can read it! But session data is saved on the server, so it's safe.
The tutorial was to create the captcha image, so I kinda didn't bother to write the whole form. You can do that yourself if you're looking into captchas!

A good link above. Thanks

I'd reiterate, that the hardest captchas to solve are those with characters joined up/overlapping

IMO, sessions aren't safe enough this way...
http://www.webmasterworld.com/php/3371366.htm
A malicious user could solve the CAPTCHA once, and then log in under that session, so he'ld just have to give in the same string every time...
 

mattura

Member
Messages
570
Reaction score
2
Points
18
Session hijacking (http://en.wikipedia.org/wiki/Session_hijacking) is a different issue. If you have security concerns in this area, CAPTCHAs are the least of your worries!
Even so, solving the CAPTCHA once does not mean it is solved the next time - every refresh will produce a different CAPTCHA and different $str. The user cannot access the $str themselves! All they have is the session id. Once solved, you can validate a post on a message board, or create a user account and be sure that it was a human that did so. A good standard security procedure for logins is to regenerate the session id against fixation attacks and session hijacking.
Most of us will use a CAPTCHA just for creation of accounts, posting of items etc, in which case storing in $_SESSION is pretty safe.
 

marshian

New Member
Messages
526
Reaction score
9
Points
0
I think you don't get my point...
For example you could have a page captcha.php, which shows a captcha and stores the $str under a certain session, and the form redirects to solvecaptcha.php, which checks wether you've given the right $str. A session-hijacker could go to captcha.php, see what the $str is and then hijack his current session so he can go to solvecaptcha.php without even having to see captcha.php (and that not once, but multiple times)
 

mattura

Member
Messages
570
Reaction score
2
Points
18
No, I'm still not sure I get your point!
A session-hijacker could go to captcha.php, see what the $str is
They could see the CAPTCHA, but otherwise, they have no way of getting at the $_SESSION['str'] (it is never sent to the client). All they can find is the session id (which is some md5-type string, and IS sent to the client). They can hijack the session all they want, but only if they have seen the CAPTCHA with their eyes will they know what the string is.

Granted, as you have pointed out, there is the possibility of multiple use for one CAPTCHA (in this example), so in this case it would be wise to destroy the $_SESSION['str'] after comparison (in solvecaptcha.php), so that the user could only use each captcha once.
If that was your point, then I got it, but just assumed users (in the same session) would naturally be prevented from posting/creating accounts/whatever in quick succession. It's probably worth pointing out though, people don't think of everything...
 
Last edited:

VPmase

New Member
Messages
914
Reaction score
0
Points
0
I use AJAX for my captcha checks. Ofcourse this is because my entire registration form is AJAX. But still even if it wasn't, I'd still use AJAX. Allows for a much more lax captcha system while still keeping bots out.
My system saves each captcha sting for an IP each time they go to the page. Lets say they end up straying from the page and the cap var changes but not the image, if they still have the old cap value it would still work xD
 

mattura

Member
Messages
570
Reaction score
2
Points
18
Fair enough.
Hopefully you have a graceful degradation for non-javascript users...
The purpose of a CAPTCHA is to stop bots, so if you still get bots, you can find out why they are getting through, then upgrade/improve the CAPTCHA system. You'd have to have a pretty damn popular site to be targeted by aggressive bots, a simple captcha is good enough for most purposes.
 

marshian

New Member
Messages
526
Reaction score
9
Points
0
"Good enough" does not exist, either you do it all right, or you just don't do it :p
 

mattura

Member
Messages
570
Reaction score
2
Points
18
I totally disagree, but let's not have this discussion here.
 

Nahid_hossain

New Member
Messages
28
Reaction score
0
Points
0
you can have some more security if you use like this:
PHP:
$_SESSION['captcha']=md5($str);
and then
PHP:
if (md5($_POST['user_entry'])==$_SESSION['captcha']) {echo "Correct!";}
 

AndrewTheAwesome

New Member
Messages
12
Reaction score
0
Points
0
Please don't bump old threads.
So if a topic is a few months old, we simply can't add relevant informaton to it? Do you want us to make a new topic entirely and reference the old one? :nuts: Weird policy. Now, if the info was irrelevant, then that would absolutely be a gratuitous "bump" and noone approves of that.
 

RossSchVle

Banned
Messages
98
Reaction score
0
Points
0
So if a topic is a few months old, we simply can't add relevant informaton to it? Do you want us to make a new topic entirely and reference the old one? :nuts: Weird policy. Now, if the info was irrelevant, then that would absolutely be a gratuitous "bump" and noone approves of that.
:rant2: it's not weird when you have to go though pages and pages off posts to try and work out what people are referring to, yes this one only has 2 pages but some have like 20+ ;)
 
Top