Hele princippet med dette CAPTCHA system er at der dannes en tekststreng på 4 tegn angivet i $tegn og den gemmes så i sessionen $_SESSION['tekst'] til senere validering.
Helt basalt dannes der et billede på 200x60 pixels der så lægges følgende lag oven på:
1. Først laves der en baggrund af tilføldige lyse pixels.
2. På den kaster vi så vores tekst som vi tilfældigt udvælger fra $tegn og gemmer i $_SESSION['tekst'].
3. Der efter kaster vi en blanding af linier og cirkler overn på teksten.
4. Til sidst kaster vi så et begrænset antal pixels overn på både tekst og linier.
Nå teksten genereres bliver den både placeret tilfældigt i forhold til hinanden (dog baseret på den forriges position) samt de får en tilfældig vinkel, skriftstørrelse og skrifttype.
De forskellige skrifttyper angives i arrayet $skrifttyper[] og kan ændres efter for godt befindende.
Meningen er så at brugeren aflæser billedet og indtaster de 4 tegn i et input-felt der sennes sammen med det andet data. Serveren kan der efter validere brugerens input ved at sammenligne det med SESSION['tekst'].
Jeg vil gennemgå de enkelte dele bid for bid men først tager vi hele scriptet her:
(Det kan også findes i en pænere .phps version her: <a href="http://www.hkb.it/ (...))
session_start();
header("Content-type: image/png");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
$skrifttyper[] = "Arial.ttf";
$skrifttyper[] = "Tahoma.ttf";
$skrifttyper[] = "Impact.ttf";
$skrifttyper[] = "Verdana.ttf";
$skrifttyper[] = "Georgia.ttf";
// Basis billedet
$billede = imagecreatetruecolor(200, 60);
// En god barggrundsstøj men kun med lyse farver
for($i=0;$i<(200*60);$i++) {
$color = imagecolorallocate($billede, mt_rand(155, 255), mt_rand(155, 255), mt_rand(155, 255));
imagesetpixel($billede, ($x - 1), $y, $color);
$x += 1;
if($x > 200) {
$y += 1;
$x = 1;
}
}
// Så selve teksten
$tegn = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz";
for($i=0;$i<4;$i++) {
$input .= substr($tegn, mt_rand(0, (strlen($tegn) - 1)), 1);
}
$_SESSION['tekst'] = $input;
$x = mt_rand(10, 60);
$y = mt_rand(35, 50);
for($i=0;$i<strlen($input);$i++) {
$color = imagecolorallocate($billede, mt_rand(0, 155), mt_rand(0, 155), mt_rand(0, 155));
$font = "C:/Windows/Fonts/".$skrifttyper[mt_rand(0, (count($skrifttyper) - 1))];
imagettftext($billede, mt_rand(25, 30), mt_rand(-10, 10), $x, $y, $color, $font, substr($input, $i, 1));
$x += mt_rand(25, 40);
$y += mt_rand(-5, 5);
}
// Derefter nogle streger og cirkler
for($i=0;$i<mt_rand(4, 6);$i++) {
$color = imagecolorallocate($billede, mt_rand(0, 200), mt_rand(0, 200), mt_rand(0, 200));
if(mt_rand(0, 2) == 1) {
imageellipse($billede, mt_rand(50, 150), mt_rand(10, 50), mt_rand(10, 60), mt_rand(10, 60), $color);
} else {
imageline($billede, mt_rand(0, 50), mt_rand(0, 60), mt_rand(100, 200), mt_rand(0, 60), $color);
}
}
// Og igen nogle tilfældige pixels overnpå for at gøre forvirringen total
for($i=0;$i<mt_rand(250, 500);$i++) {
$color = imagecolorallocate($billede, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
imagesetpixel($billede, mt_rand(0, 200), mt_rand(0, 60), $color);
}
imagepng($billede);
?>
Det var lidt af en mundfuld men lad os tage det lidt af gangen.
Det basale
Først kører vi session_start() så vi er klar når vi skal gemme vores teksstreng i en session. Der erfter angiver vi et par headers der fortætter at det er et png billede og at det ikke skal gemmes i browserens cache (er så op til den enkelte browser om den vil godtage det). Vi definerer også lide de skrifttyper vi vil bruge i arrayet $skrifttyper.
Her efter danner vi et 'true color' billede på 200x60 px til at lave vores CAPTCHA på.
header("Content-type: image/png");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
$skrifttyper[] = "Arial.ttf";
$skrifttyper[] = "Tahoma.ttf";
$skrifttyper[] = "Impact.ttf";
$skrifttyper[] = "Verdana.ttf";
$skrifttyper[] = "Georgia.ttf";
// Basis billedet
$billede = imagecreatetruecolor(200, 60);
Baggrundsstøj
En af de bedste måder at skjule teksten på er at lave masser af baggrundsstøj da det er meget nemmere for an computer at læse tekst på en ensfarvet baggrund. Dette gøres ved at vi kører samtlige pixels på billedet igenem og giver dem en tilfældig farve med imagesetpixel(). Jeg har dog valgt kun at bruge farver fra den lysere del af skalaen så de ikke blander sig for meget med teksten.
$color = imagecolorallocate($billede, mt_rand(155, 255), mt_rand(155, 255), mt_rand(155, 255));
imagesetpixel($billede, ($x - 1), $y, $color);
$x += 1;
if($x > 200) {
$y += 1;
$x = 1;
}
}
Selve teksten
Selve teksten genereres ved at vælge 4 tilfældige tegn ud fra den teksstreng der angiver hvilke tegn vi vil bruge. Bag efter vælger vi så et startpunkt for teksten med kordinaterne $x, $y.
for($i=0;$i<4;$i++) {
$input .= substr($tegn, mt_rand(0, (strlen($tegn) - 1)), 1);
}
$x = mt_rand(10, 60);
$y = mt_rand(35, 50);
Her efter kaster vi den ind på billedet med en tilfældig frave (dog kun mørke farver der ikke blander sig med baggrundsstøjen), en tilfældig skrifttype, en tilfældig vinkel, en tilfældig skriftstørrelse og på position $x, $y.
Bag efter angiver vi en ny position for $x, $y (der vil være placeringen af det næste tegn). $x går fra 25 - 50 da bogstaverne jo ikke bare kan spredes tilfældigt men skal stå i rækkefølge hvor imod $y går fra -5 - 5 da bogstaverne både kan placeres lidt nede og oppe i forhold til hinanden.
Til sidst gemes det enkelte bogstav i $_SESSION['tekst'] til senere validering.
$color = imagecolorallocate($billede, mt_rand(0, 155), mt_rand(0, 155), mt_rand(0, 155));
$font = "C:/Windows/Fonts/".$skrifttyper[mt_rand(0, (count($skrifttyper) - 1))];
imagettftext($billede, mt_rand(25, 30), mt_rand(-10, 10), $x, $y, $color, $font, substr($input, $i, 1));
$x += mt_rand(25, 40);
$y += mt_rand(-5, 5);
$_SESSION['tekst'] .= substr($input, $i, 1);
}
Streger og Cirkler
Her efter genereres der nogle streger og cirkler oven på vores tekst. Der vælges tilfældigt om der skal laves en streg eller en cirkel, der er dog dobbelt så stor sansynlighed for at tegne en streg.
$color = imagecolorallocate($billede, mt_rand(0, 200), mt_rand(0, 200), mt_rand(0, 200));
if(mt_rand(0, 2) == 1) {
imageellipse($billede, mt_rand(50, 150), mt_rand(10, 50), mt_rand(10, 60), mt_rand(10, 60), $color);
} else {
imageline($billede, mt_rand(0, 50), mt_rand(0, 60), mt_rand(100, 200), mt_rand(0, 60), $color);
}
}
Tilfældige Pixels
Til sidst kaster vi en masse blandede pixels ud over det hele for at skjule bogstaverne yderligere. Dette gøres på samme måde som i starten bare 100% tilfældigt og med brug af hele fravespekteret.
$color = imagecolorallocate($billede, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255));
imagesetpixel($billede, mt_rand(0, 200), mt_rand(0, 60), $color);
}
Som det sidste kaster vi så det genererede billede afsetd til brugeren.
I praksis brug
Et eksempel på brug af billedet i praksis kan ses her: <a href="http://www.hkb.it/ (...)
Billedet der sendes burde nu kun kunne læses af mennesker. Jeg har testet det op imod "What the font" (<a href="http://www.myfonts.com/ (...)) og den har aldrig været i stand til bare at komme tæt på bare et af bogstaverne.
En god ide når man bruger det i praksis er at give brugeren mulighed for at kunne reloade billedet hvis det er for utydeligt. Det kan gøres med denne lille smule JavaScript.
Er billedet for utydeligt? så < a href="#" onclick="document.getElementById('CAPTCHA').src='http://www.hkb.it/ (...) + Math.random();">klik her</ a>.
Det er også grunden til at jeg genererer $_SESSION['tekst'] i selve billedet.
Yderligere kommentarer
Da jeg bruger så mange tilfældige værdier har jeg bevist valgt at bruge mt_rand() istedet for bare at bruge rand(). Det giver faktisk en målbar forskel på genereringen af billedet.
Hvis din hjemmeside hostes på en server du ikke selv kan administrere har du højest sansynligt ikke adgang til C:/Windows/Fonts/ så du må eventuelt prøve en anden sti. Jeg har endnu ikke testet om imagettftext() vil fungere hvis skrifttypen ikke er instaleret på serveren men bare ligger i en tilfældig mappe.
Svar
obhat: Umiddelbart vil jeg ikke tro det da selve billefunktionerne bruger GDlib som er "indbygget" i PHP men om ASP har en lignende tilføjelse ved jeg ikke.
windcape: Jahr i know... er derfor jeg også har lavet den i .phps format: <a href="http://www.hkb.it/ (...)


