Page 1 of 2

Captcha Efficiency

Posted: Sat Dec 15, 2007 9:54 pm
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)?

Posted: Sat Dec 15, 2007 10:17 pm
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.

Posted: Sat Dec 15, 2007 11:44 pm
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.

Posted: Sun Dec 16, 2007 12:41 am
by Zoxive
I stopped making my own captchas and started using http://recaptcha.net/

Posted: Sun Dec 16, 2007 8:02 am
by feyd
Way too easy to break.

Posted: Sun Dec 16, 2007 9:29 am
by John Cartwright
Zoxive wrote:I stopped making my own captchas and started using http://recaptcha.net/
Where have you been all my life?

Posted: Sun Dec 16, 2007 12:18 pm
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.

Posted: Sun Dec 16, 2007 1:00 pm
by Benjamin
If I saw that I probably wouldn't bother signing up.

Posted: Sun Dec 16, 2007 1:12 pm
by Chalks
astions wrote:If I saw that I probably wouldn't bother signing up.
It doesn't HAVE to be pretty. :P





... yet.

Posted: Sun Dec 16, 2007 1:19 pm
by John Cartwright
astions wrote:If I saw that I probably wouldn't bother signing up.
Seems readable to me. :?

Posted: Sun Dec 16, 2007 1:36 pm
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.

Posted: Sun Dec 16, 2007 1:40 pm
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.

Posted: Sun Dec 16, 2007 2:02 pm
by feyd
I'd say it's still easy to break.

Posted: Sun Dec 16, 2007 2:05 pm
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?

Posted: Sun Dec 16, 2007 2:09 pm
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.