You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
8.0 KiB
244 lines
8.0 KiB
<?php
|
|
/**
|
|
* OWASP PureCaptcha
|
|
* Generates simple CAPTCHAs without requiring any third party library.
|
|
* @version 1.1
|
|
*/
|
|
|
|
namespace OWASP;
|
|
|
|
class PureCaptcha
|
|
{
|
|
protected $charWidth=6;
|
|
protected $charHeight=13;
|
|
protected $chars="2346789ABDHKLMNPRTWXYZ"; //do not modify!
|
|
protected $ascii="eNrtW0FuwyAQ/NIANjbOa3LMG6r8vWrrSonkkt0xDWDvIcGXEYHM7O6s4b
|
|
p4v3zcFlyuiwu/T/Hn4ba4h49fx7COwzqO3+P964tF+i0kViRWJLaQ4RGJF3PiETnQyFGzzidklC
|
|
A31znlkMjt7Zzb2+y/kjQ79MwEbEH/+kOftsjxLHKehK7cbYT/qu0KNBcHmosjzcVI63yi1znTyE
|
|
RHCAd6oZX479uL/yIuBho5SFgs5z8kc0YaOUm4iJfxXxVbEr23Djy0Dv+D1T/iXzvSyKjhop7/r+
|
|
M/LP5v83+w+qdM/aOP/6LKqXr9r0Lm+Z+H1uH/aPGf87/Y7X8t/jfA/2j8b43/MP/7Pv5P7frfbH
|
|
YPtKOszn8VcqIrxPrxXwetw/+5n/pfz395/Y8L2//HG+sf1ZwzjUw0Utf/byH+J+N/bf7L47/xv/
|
|
z7L7QnAFG+7NgAgDYAnRngHgog50wAXAcU9BtgaBqDEz3nTK/zVALw/QiAbwHxFvg/X4G53QJwuw
|
|
VgR4CCZYBc9wh0BpD3gKDuAVkJVE4Aw5EEgGICcF0JgG+C4vQCGI/QBTqWCT5cCSSDVhJANAH0Kw
|
|
AzwfsFMB3xHBzEJliFLHwPoMY5OBEy0cj8OWi0mAFmM8G1D0LwHgC0CYbaBIvaBB1mgHRuAajOEB
|
|
W+CcNnANGcM408UwnkYQLoTwBWApUTgN0F7vAuDNRdILsLvymA+ycgmwSd";
|
|
function __construct()
|
|
{
|
|
$this->ascii=unserialize(gzuncompress(base64_decode(
|
|
preg_replace('/\s+/', '', $this->ascii))));
|
|
$this->text=$this->randomText();
|
|
|
|
}
|
|
|
|
/**
|
|
* Contains the captcha text.
|
|
* @var string
|
|
*/
|
|
public $text;
|
|
|
|
/**
|
|
* Generates random text for use in captcha
|
|
* @param integer $length
|
|
* @return string
|
|
*/
|
|
protected function randomText($length=5)
|
|
{
|
|
$res="";
|
|
for ($i=0;$i<$length;++$i)
|
|
$res.=$this->chars[mt_rand(0,strlen($this->chars)-1)];
|
|
return $res;
|
|
}
|
|
/**
|
|
* returns the index of a char in $chars array
|
|
*/
|
|
protected function asciiEntry($char)
|
|
{
|
|
for ($i=0;$i<strlen($this->chars);++$i)
|
|
if ($this->chars[$i]==$char) return $i;
|
|
return -1;
|
|
|
|
}
|
|
/**
|
|
* converts a text to a bitmap
|
|
* which is a 2D array of ones and zeroes denoting the text
|
|
*/
|
|
protected function textBitmap($text,$spacing=2)
|
|
{
|
|
$width=$this->charWidth;
|
|
$height=$this->charHeight;
|
|
$result=array();
|
|
$baseY=$baseX=0;
|
|
|
|
for ($index=0;$index<strlen($text);++$index)
|
|
{
|
|
for ($j=0;$j<$height;++$j)
|
|
{
|
|
for ($i=0;$i<$width;++$i)
|
|
$result[$baseY+$j][$baseX+$i]=
|
|
1-$this->ascii[$this->asciiEntry($text[$index])][$j][$i];
|
|
for ($i=0;$i<$spacing;++$i)
|
|
$result[$baseY+$j][$baseX+$width+$i]=0;
|
|
}
|
|
$baseX+=$width+$spacing;
|
|
}
|
|
return $result;
|
|
}
|
|
/**
|
|
* displays a bitmap string on the browser screen
|
|
*/
|
|
protected function displayBitmap($bitmap)
|
|
{
|
|
header("Content-Type: image/bmp");
|
|
echo $this->bitmap2bmp($bitmap);
|
|
}
|
|
|
|
protected function inlineBitmap($bitmap)
|
|
{
|
|
return base64_encode($this->bitmap2bmp($bitmap));
|
|
}
|
|
|
|
/**
|
|
* generates a monochrome BMP file
|
|
* a bitmap needs to be sent to this function
|
|
* i.e a 2D array with every element being either 1 or 0
|
|
* @param integer $width
|
|
* @param integer $height
|
|
* @param array $bitmap
|
|
* @return string
|
|
*/
|
|
protected function bitmap2bmp($bitmap)
|
|
{
|
|
$width=count($bitmap[0]);
|
|
$height=count($bitmap);
|
|
$bytemap=$this->bitmap2bytemap($bitmap);
|
|
|
|
$rowSize=floor(($width+31)/32)*4;
|
|
$size=$rowSize*$height + 62; //62 metadata size
|
|
#bitmap header
|
|
$data= "BM"; //header
|
|
$data.= (pack('V',$size)); //bitmap size ,4 bytes unsigned little endian
|
|
$data.= "RRRR";
|
|
$data.= (pack('V',14+40+8)); //bitmap data start offset ,
|
|
//4 bytes unsigned little endian, 14 forced, 40 header, 8 colors
|
|
|
|
#info header
|
|
$data.= pack('V',40); //bitmap header size (min 40),
|
|
//4 bytes unsigned little-endian
|
|
$data.= (pack('V',$width)); //bitmap width , 4 bytes signed integer
|
|
$data.= (pack('V',$height)); //bitmap height , 4 bytes signed integer
|
|
$data.= (pack('v',1)); //number of colored plains , 2 bytes
|
|
$data.= (pack('v',1)); //color depth , 2 bytes
|
|
$data.= (pack('V',0)); //compression algorithm , 4 bytes (0=none, RGB)
|
|
$data.= (pack('V',0)); //size of raw data, 0 is fine for no compression
|
|
$data.= (pack('V',11808)); //horizontal resolution (dpi), 4 bytes
|
|
$data.= (pack('V',11808)); //vertical resolution (dpi), 4 bytes
|
|
$data.= (pack('V',0)); //number of colors in pallette (0 = all), 4 bytes
|
|
$data.= (pack('V',0)); //number of important colors (0 = all), 4 bytes
|
|
|
|
#color palette
|
|
$data.= (pack('V',0x00FFFFFF)); //next color, white
|
|
$data.= (pack('V',0)); //first color, black
|
|
|
|
for ($j=$height-1;$j>=0;--$j)
|
|
for ($i=0;$i<$rowSize/4;++$i)
|
|
for ($k=0;$k<4;++$k)
|
|
if (isset($bytemap[$j][$i*4+$k]))
|
|
$data.= pack('C',$bytemap[$j][$i*4+$k]);
|
|
else
|
|
$data.= pack('C',0);
|
|
return $data;
|
|
}
|
|
/**
|
|
* Converts a bitmap to a bytemap, which is necessary for outputting it
|
|
*
|
|
*/
|
|
protected function bitmap2bytemap($bitmap)
|
|
{
|
|
$width=count($bitmap[0]);
|
|
$height=count($bitmap);
|
|
$bytemap=array();
|
|
for ($j=0;$j<$height;++$j)
|
|
{
|
|
for ($i=0;$i<$width/8;++$i)
|
|
{
|
|
$bitstring="";
|
|
for ($k=0;$k<8;++$k)
|
|
if (isset($bitmap[$j][$i*8+$k]))
|
|
$bitstring.=$bitmap[$j][$i*8+$k];
|
|
else
|
|
$bitstring.="0";
|
|
$bytemap[$j][]=bindec($bitstring);
|
|
}
|
|
}
|
|
return $bytemap;
|
|
}
|
|
/**
|
|
* rotates a bitmap, returning new dimensions with the bitmap
|
|
* return bitmap
|
|
*/
|
|
protected function rotateBitmap($bitmap, $degree)
|
|
{
|
|
$c=cos(deg2rad($degree));
|
|
$s=sin(deg2rad($degree));
|
|
|
|
$width=count($bitmap[0]);
|
|
$height=count($bitmap);
|
|
$newHeight=round(abs($width*$s)+abs($height*$c));
|
|
$newWidth=round(abs($width*$c) + abs($height*$s))+1;
|
|
$x0 = $width/2 - $c*$newWidth/2 - $s*$newHeight/2;
|
|
$y0 = $height/2 - $c*$newHeight/2 + $s*$newWidth/2;
|
|
$result=array_fill(0, $newHeight, array_fill(0, $newWidth, 0));
|
|
for ($j=0;$j<$newHeight;++$j)
|
|
for ($i=1;$i<$newWidth;++$i)
|
|
{
|
|
$y=(int)(-$s*$i+$c*$j+$y0);
|
|
$x=(int)($c*$i+$s*$j+$x0);
|
|
if (isset($bitmap[$y][$x]))
|
|
$result[$j][$i]=$bitmap[$y][$x];
|
|
}
|
|
return $result;
|
|
}
|
|
/**
|
|
* scales a bitmap to be bigger
|
|
*/
|
|
protected function scaleBitmap($bitmap,$scaleX,$scaleY)
|
|
{
|
|
$width=count($bitmap[0]);
|
|
$height=count($bitmap);
|
|
$newHeight=$height*$scaleY;
|
|
$newWidth=$width*$scaleX;
|
|
$result=array_fill(0, $newHeight, array_fill(0, $newWidth, 0));
|
|
for ($j=0;$j<$newHeight;++$j)
|
|
for ($i=0;$i<$newWidth;++$i)
|
|
$result[$j][$i]=$bitmap[(int)($j/$scaleY)]
|
|
[(int)($i/$scaleX)];
|
|
return $result;
|
|
}
|
|
/**
|
|
* adds random noise to the captcha
|
|
*/
|
|
protected function distort($bitmap,$noisePercent=5)
|
|
{
|
|
for ($j=0;$j<count($bitmap);++$j)
|
|
for ($i=0;$i<count($bitmap[0]);++$i)
|
|
if (isset($bitmap[$j][$i]) && mt_rand()%100<$noisePercent)
|
|
$bitmap[$j][$i]=1;
|
|
return $bitmap;
|
|
}
|
|
|
|
/**
|
|
* draw a captcha to the screen, returning its value
|
|
*/
|
|
public function show($distort=true,$scale=2.3)
|
|
{
|
|
$bitmap=$this->textBitmap($this->text);
|
|
$degree=mt_rand(2,4);
|
|
if (mt_rand()%100<50)
|
|
$degree=-$degree;
|
|
$bitmap=$this->rotateBitmap($bitmap,$degree);
|
|
$bitmap=$this->scaleBitmap($bitmap,$scale,$scale);
|
|
if ($distort) $bitmap=$this->distort($bitmap);
|
|
return $this->inlineBitmap($bitmap);
|
|
}
|
|
|
|
}
|
|
|