Captcha Efficiency

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

User avatar
Chalks
Forum Contributor
Posts: 447
Joined: Thu Jul 12, 2007 7:55 am
Location: Indiana

Captcha Efficiency

Post by Chalks »

captcha/captcha.php

Code: Select all

<?php
// Much thanks to
// http://www.codewalkers.com/c/a/Miscella ... -with-PHP/
// for the great tutorial on this.  

putenv('GDFONTPATH=' . realpath('.'));
$font = 'Acidic';
// Above lines because php manual said to
// http://us.php.net/imagettftext

$md5_hash = md5(rand(0,999));
$code = substr($md5_hash, 15, 7);
$_SESSION['code'] = $code;
// Generate pseudo random string 7 characters long

$width = 130;
$height = 60;
$image = ImageCreate($width, $height);
// Create image

$white = ImageColorAllocate($image, 255, 255, 255);
$black = ImageColorAllocate($image, 0, 0, 0);
$grey = ImageColorAllocate($image, 204, 204, 204);
$red =  ImageColorAllocate($image, 255, 0, 0);
// Create colors

ImageFill($image, 0, 0, $black);
imagettftext($image, 18, rand(-10,10), rand(2,35), rand(32,45), $white, $font, $code);
imageline($image, rand(2,128), rand(2, 58), rand(2,128), rand(2, 58), $red);
imageline($image, rand(2,128), rand(2, 58), rand(2,128), rand(2, 58), $red);
imageline($image, rand(2,128), rand(2, 58), rand(2,128), rand(2, 58), $red);
imageline($image, rand(2,128), rand(2, 58), rand(2,128), rand(2, 58), $red);
imageline($image, rand(2,128), rand(2, 58), rand(2,128), rand(2, 58), $red);
ImageRectangle($image,0,0,$width-1,$height-1,$grey);
ImageRectangle($image,0,0,$width-2,$height-2,$grey);
// Draw stuff

header("Content-Type: image/jpeg");
// Modify headers for FUN.  >.>

ImageJpeg($image);
ImageDestroy($image);
// That's right.  Destroy it.
?>
Then, in my register.php page (which has session_start() at the beginning)

Code: Select all

<?php
  if(isset($_GET['reg']) && $_GET['reg']==yep)  // show register form
  {
?>

<img id="captcha" src="captcha/captcha.php" />

<?php
  }
?>
Outputs something like:
Image


I have two questions about this code.
The first is: how efficient is it to draw 5 lines like that with 4 calls to rand() in each one... Is there a better way?
The second is: Is this a decent captcha? Or should it be more awesome in some way (suggestions for improvement needed, that is)?
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Looks decent. One thing that jumps out at me is you are blindly using the session variable and assuming that it has been initialized. Typically since the captcha is generated on it's own HTTP request you can start the session in your script without worrying about an existing session.

To be safe, I'd do something like at the top of your script

Code: Select all

if (!session_id()) session_start();
Good captures consist of as much variance as you can throw at them. If it can be predicted then you've failed.

Couple of things you might consider

- Using background(s) images and randomly rotating them
- Randomizing pitch, colour, size, font type, character length of the font
- Randomizing between different shapes generated (i.e. don't just use lines, use squares, circles, etc. or even use several different shapes together)

It's a fine line between usability and security with captchas.
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

I think the letters contrast against the background too much, which is okay if the letters are distorted enough, but yours are not very distorted.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
User avatar
Zoxive
Forum Regular
Posts: 974
Joined: Fri Apr 01, 2005 4:37 pm
Location: Bay City, Michigan

Post by Zoxive »

I stopped making my own captchas and started using http://recaptcha.net/
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

Way too easy to break.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

Zoxive wrote:I stopped making my own captchas and started using http://recaptcha.net/
Where have you been all my life?
User avatar
Chalks
Forum Contributor
Posts: 447
Joined: Thu Jul 12, 2007 7:55 am
Location: Indiana

Post by Chalks »

Thanks to all the responses. I've updated my code quite a bit:

Code: Select all

<?php
// Much thanks to
// http://www.codewalkers.com/c/a/Miscella ... -with-PHP/
// for the tutorial on this.  
// Super much thanks to the people over at devnetwork
// http://forums.devnetwork.net/viewtopic.php?t=76934
// for making my captcha more awesome.  


if(session_id()=="")
  session_start();
// Because Jcart said to

putenv('GDFONTPATH=' . realpath('.'));
// Because php manual said to
// http://us.php.net/imagettftext

$md5_hash = md5(rand(0,999));
$code = substr($md5_hash, 15, 6);
$_SESSION['code'] = $code;
// Generate pseudo random string 6 characters long

$i =  genImage();
// Create image

$black = ImageColorAllocate($i, 0, 0, 0);
$grey = ImageColorAllocate($i, 204, 204, 204);
// Create colors

genText($code, $i);
for($z = 0; $z < rand(3,6); $z++)  // draws 3-6 random things
  genStuff($i);
ImageRectangle($i,0,0,$width-1,$height-1,$grey);
ImageRectangle($i,0,0,$width-2,$height-2,$grey);
// Draw stuff

header("Content-Type: image/jpeg");
// Modify headers for FUN.  >.>

ImageJpeg($i);
ImageDestroy($i);
// That's right.  Destroy it.


function genStuff($i)
{
  $stuffChooser = rand(1,3);
  switch ($stuffChooser)
  {
    case 1:
      imageline($i, rand(2,128), rand(2, 58), rand(2,128), rand(2, 58), colr($i));
      break;
    case 2:
      imagearc($i, rand(2,128), rand(2, 58), rand(5, 50), rand(5, 50),  0, 360, colr($i));
      break;
    default:
      ImageRectangle($i, rand(2, 128), rand(2, 58), rand(2, 128), rand(2, 58), colr($i));
  }
}

function colr($i)
{
  $colorChooser = rand(1, 6);
  switch ($colorChooser)
  {
    case 1:
      $c = ImageColorAllocate($i, 200, 160, 160);
      break;
    case 2:
      $c = ImageColorAllocate($i, 0, 255, 0);
      break;
    case 3:
      $c = ImageColorAllocate($i, 50, 50, 255);
      break;
    case 4:
      $c = ImageColorAllocate($i, 255, 255, 0);
      break;
    case 5:
      $c = ImageColorAllocate($i, 0, 255, 255);
      break;
    default:
      $c = ImageColorAllocate($i, 255, 0, 255);
  }
  return $c;
}

function setFont()
{
  $fontChooser = rand(1, 5);
  switch ($fontChooser)
  {
    case 1:
      $f = "Acidic";
      break;
    case 2:
      $f = "FifteenOkay";
      break;
    case 3:
      $f = "FiveFingerDiscount";
      break;
    case 4:
      $f = "Flug";
      break;
    default:
      $f = "RomanAcid";
      break;
  }
return $f;
}

function genImage()
{
  $imgChooser = rand(1, 6);
  switch ($imgChooser)
  {
    case 1:
      $imager = imagecreatefromjpeg('1.jpg');
      break;
    case 2:
      $imager = imagecreatefromjpeg('2.jpg');
      break;
    case 3:
      $imager = imagecreatefromjpeg('3.jpg');
      break;
    case 4:
      $imager = imagecreatefromjpeg('4.jpg');
      break;
    case 5:
      $imager = imagecreatefromjpeg('5.jpg');
      break;
    default:
      $imager = imagecreatefromjpeg('6.jpg');
  }
  return $imager;
}

function genText($t, $i)
{
  $text = str_split($t, 1);
  $x = 1;
  $x2 = 10;
  foreach($text as $tz)
  {
    imagettftext($i, 22, rand(-20,20), rand($x,$x2), rand(35,45), colr($i), setFont(), $tz);
    $x = $x + 20;
    $x2 = $x2 + 20;
  }
}
?>
Produces something like:
Image

The image is displayed the same way that it was in my first post.




I'm still somewhat concerned with the overhead on this captcha. I potentially use all 5 different fonts along with calling rand(), ImageColorAllocate(), and a few other functions several times. Is this overkill?





EDIT: I found a small... let's call it "feature"... with this script. If I pull the image using html img tags, the session will not have the correct variable until the page is refreshed. However, since I'm using this for form validation, it doesn't matter, because once the user clicks "submit", the code in the session is the correct one.
Last edited by Chalks on Sun Dec 16, 2007 1:07 pm, edited 1 time in total.
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

If I saw that I probably wouldn't bother signing up.
User avatar
Chalks
Forum Contributor
Posts: 447
Joined: Thu Jul 12, 2007 7:55 am
Location: Indiana

Post by Chalks »

astions wrote:If I saw that I probably wouldn't bother signing up.
It doesn't HAVE to be pretty. :P





... yet.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

astions wrote:If I saw that I probably wouldn't bother signing up.
Seems readable to me. :?
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Post by Benjamin »

I guess I'm not a big fan of captchas. It does look good though. I would be concerned about people who have issues with seeing certain colors. Might want to convert a few of those to grayscale and see if you can still make out the letters. 10% of males and 1% of females have some sort of color blindness.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

astions wrote:I guess I'm not a big fan of captchas. It does look good though. I would be concerned about people who have issues with seeing certain colors. Might want to convert a few of those to grayscale and see if you can still make out the letters. 10% of males and 1% of females have some sort of color blindness.
It is always a good idea to provide alternatives to captchas to not discriminate against people with impairments. A simple audio alternative would be quite easy to mock up.
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

I'd say it's still easy to break.
User avatar
John Cartwright
Site Admin
Posts: 11470
Joined: Tue Dec 23, 2003 2:10 am
Location: Toronto
Contact:

Post by John Cartwright »

I'm not overly familiar with the operations of OCR -- only the basics. Can you provide some insight on certain things we should consider when developing captchas to combat OCR?
User avatar
s.dot
Tranquility In Moderation
Posts: 5001
Joined: Sun Feb 06, 2005 7:18 pm
Location: Indiana

Post by s.dot »

You can add as much entropy and lines as you would like, but if the letters/numbers are not distorted more (twisted, twirled.. some form of contortion), an edge detection program could easily make out each character. The use of different fonts helps this out a little bit.

Overall, you have a pretty good captcha! I'd say a lot of attempts to guess what it is with a program would fail, except one that simply attempts to detect the outlines of the characters.

Take a look at the captchas on the recaptcha site to get a good idea about distortion.
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.
Post Reply