RSS Feed

Peter Goodman's blog about PHP, Parsing Theory, C++, Functional Programming, Applications,

Simple CAPTCHA

So I made this CAPTCHA script a little while ago for OneLobby. I have no idea how strong it is, but I took a different approach to obfuscating the image. Normally one would put lot's of lines and shapes and whatnot in the image as extra noise. I didn't feel like doing this this time around so I messed around with the quality of the image instead... (skip to the end if you want to download the .zip)

You can find an example of the CAPTCHA script if you click here.

<?php

/*********************************************************************************\
 *
 *     (c) Copyright 2006, Peter Goodman, all rights reserved
 *
 *     Permission is hereby granted, free of charge, to any person obtaining 
 *     a copy of this software and associated documentation files (the 
 *     "Software"), to deal in the Software without restriction, including 
 *     without limitation the rights to use, copy, modify, merge, publish, 
 *     distribute, sublicense, and/or sell copies of the Software, and to 
 *     permit persons to whom the Software is furnished to do so, subject to 
 *     the following conditions:
 *
 *     The above copyright notice and this permission notice shall be 
 *     included in all copies or substantial portions of the Software.
 *
 *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 *     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
 *     BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
 *     ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 *     CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 *     SOFTWARE.
\*********************************************************************************/

//--------------------------------------------
// A little class to make and deal with captcha
// images.
//--------------------------------------------

class Captcha
{
    private $font = 'Action Man Bold'; // make sure to exclude .ttf
    private $captcha_length = 6;
    private $reg_id = NULL;
    private $use_captcha = FALSE;
    private $font_dir;

    //--------------------------------------------
    // Constructor.
    //--------------------------------------------

    public function __construct()
    {
        if(!session_id() || session_id() == '')
        {
            session_start();
        }

        $this->reg_id = md5(uniqid(""));
        $this->use_captcha = function_exists('imagecreate');

        // directory where the font is in
        $this->font_dir = dirname(__FILE__) .'/';
    }

    //--------------------------------------------
    // Create the captcha image itself. It is a JPEG.
    //--------------------------------------------

    public function createCaptcha()
    {
        //--------------------------------------------
        // Make sure that something is actually using this
        // captcha.
        //--------------------------------------------

        if(!$this->captchaIsRegistered() || !$this->use_captcha)
        {
            header("HTTP/1.0 404 Not Found");
            exit();
        }

        //--------------------------------------------
        // Unregister this captcha.
        //--------------------------------------------

        $this->unregisterCaptcha();

        //--------------------------------------------
        // Deal with the font path.
        //--------------------------------------------

        putenv('GDFONTPATH='. realpath($this->font_dir));

        //--------------------------------------------
        // Do all of the headers.
        //--------------------------------------------

        header("Content-type: image/jpeg");
        header(base64_decode("WC1DYXB0Y2hhOiBPbmVMb2JieSBodHRwOi8vd3d3Lm9uZWxvYmJ5LmNvbQ=="));
        header("Expires: Mon, 23 Jul 1993 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");


        //--------------------------------------------
        // Create the base image 95x40 px.
        //--------------------------------------------

        $create_fn = function_exists('imagecreatetruecolor') ? 'imagecreatetruecolor' : 'imagecreate';

        $final_image = $create_fn(95, 40);

        //--------------------------------------------
        // Create a solid white background. If background
        // junk was to be used, it would probably be added
        // in here.
        //--------------------------------------------

        $background = imagecolorallocate($final_image, 255, 255, 255);
        imagefill($final_image, 0, 0, $background);
        imagecolordeallocate($final_image, $background);

        unset($background);

        //--------------------------------------------
        // Figure out the code.
        //--------------------------------------------

        $code = strtoupper(substr(md5(uniqid("")), 0, $this->captcha_length));

        //--------------------------------------------
        // Add the code the session.
        //--------------------------------------------

        $_SESSION['captcha_code'] = $this->hashInput($code);

        //--------------------------------------------
        // Add the code in.
        //--------------------------------------------

        // stuff for the letter itself
        $letter_width = 14;
        $letter_height = 20;

        for($i = 0; $i < $this->captcha_length; $i++) {

            // stuff for the letter itself
            $letter_text = $code{$i};

            // positioning
            $layer_x = ($i * $letter_width);
            $layer_y = 32 + rand(-7, 7);

            $font_color = imagecolorallocate($final_image, rand(0,150), rand(0,150), rand(0,150));
            imagettftext($final_image, 20, rand(-5, 5), $layer_x, $layer_y, $font_color, $this->font, $letter_text);
            imagecolordeallocate($final_image, $font_color);
        }

        unset($font_color);

        //--------------------------------------------
        // Interlace the image for better output in the
        // browser.
        //--------------------------------------------

        imageinterlace($final_image, 1);

        //--------------------------------------------
        // Output the final image in very low quality.
        //--------------------------------------------

        imagejpeg($final_image, FALSE, 15);

        //--------------------------------------------
        // Clean up the memory
        //--------------------------------------------

        imagedestroy($final_image);
        unset($final_image);
    }

    //--------------------------------------------
    // Create all the HTML needed to use this captcha
    // so that we can just plunk in {$captcha_img},
    // etc. where it's stylish and everything
    // will work.
    //--------------------------------------------

    public function drawCaptcha()
    {
        //--------------------------------------------
        // If a captcha has already been passed, we don't
        // need to display any others.
        //--------------------------------------------

        if($this->passedAlready())
        {
            // if you want it to not display the captcha a second time,
            // don't use this.
            //return;
        }

        //--------------------------------------------
        // Register the captcha.
        //--------------------------------------------

        $this->registerCaptcha();

        //--------------------------------------------
        // Path to the image. The 'reg_id' needs to be
        // passed to this!!
        // *** might want to change this ***
        //--------------------------------------------

        http://www.ioreader.com = 'index.php?act=captcha&reg_id='. $this->reg_id;

        //--------------------------------------------
        // Set the html to the templates.
        //--------------------------------------------

        $html = '';
        $html .= '<div style="width:95px;height:40px;background:url(/2006/12/16/simple_captcha//index.html) top left no-repeat;"> </div>';
        $html .= '<input type="text" name="captcha_input" id="captcha_input" value="" maxlength="'. $this->captcha_length .'" />';

        if($this->use_captcha)
        {
            echo $html;
        }
    }

    //--------------------------------------------
    // Validate a the captcha image with the session.
    //--------------------------------------------

    public function isValid()
    {       
        //--------------------------------------------
        // If we can't use captcha's then set the valid to
        // default to TRUE.
        //--------------------------------------------

        $valid = !$this->use_captcha ? TRUE : FALSE;

        //--------------------------------------------
        // Validate the request variable against the
        // session variable.
        //--------------------------------------------

        if(!$valid && isset($_SESSION['captcha_code']))
        {
            if($this->hashInput($_POST['captcha_input']) == $_SESSION['captcha_code'])
            {
                $valid = TRUE;

                $_SESSION['captcha_passed'] = TRUE;
            }
        }

        //--------------------------------------------
        // Reset the value to nothing, which means that this
        // function can and should only be used once.
        //--------------------------------------------

        $_SESSION['captcha_code'] = NULL;

        //--------------------------------------------
        // So... is it valid?
        //--------------------------------------------

        return $valid;
    }

    //--------------------------------------------
    // Register a captcha.
    //--------------------------------------------

    private function registerCaptcha()
    {
        $_SESSION['captcha_registered'] = $this->reg_id;
    }

    //--------------------------------------------
    // Unregister a captcha.
    //--------------------------------------------

    private function unregisterCaptcha()
    {
        $_SESSION['captcha_registered'] = FALSE;
    }

    //--------------------------------------------
    // Check to see if the captcha is in use, otherwise
    // we don't want to be able to see the image.
    //--------------------------------------------

    private function captchaIsRegistered()
    {       
        $sess_reg_id = isset($_SESSION['captcha_registered']) ? $_SESSION['captcha_registered'] : FALSE;
        $req_reg_id = isset($_GET['reg_id']) ? $_GET['reg_id'] : FALSE;

        return ($sess_reg_id && $req_reg_id && $sess_reg_id == $req_reg_id);
    }

    //--------------------------------------------
    // Check to see if a captcha has been passed already.
    //--------------------------------------------

    private function passedAlready()
    {
        $passed = FALSE;

        if(isset($_SESSION['captcha_passed']) && $_SESSION['captcha_passed'])
        {
            $passed = TRUE;
        }

        return $passed;
    }

    //--------------------------------------------
    // Hash some input with some salt.
    //--------------------------------------------

    private function hashInput($in)
    {
        return md5('d41d8cd98f00b204e9800998ecf8427e' . $in);
    }
}

And here is an example of it in use...

<?php

session_start();

require_once dirname(__FILE__) .'/captcha.php';

$captcha = new Captcha;

$act = isset($_GET['act']) ? $_GET['act'] : '';

switch($act)
{
    // output the captcha image
    case 'captcha':
    {
        $captcha->createCaptcha();
        break;
    }

    // validate the form input
    case 'check':
    {
        if($captcha->isValid())
        {
            echo 'code is valid';
        }
        else
        {
            echo 'code is invalid';
        }
        break;
    }

    // display a form
    case '':
    default:
    {
        echo '<html><head></head><body>';
        echo '<form action="index.php?act=check" method="post" enctype="multipart/form-data">';
        $captcha->drawCaptcha();
        echo '</form>';
        echo '</body></html>';
    }
}

If you want to download the simple CAPTCHA class and related files, click here.


Comments


Comment