Also, in diesem Tutorial möchte ich euch ein kleines Beispiel für einen relativ einfachen Captcha zeigen. Mit diesem Thema musste ich mich auseinandersetzten, nachdem auf "meiner" Homepage automatische Einträge im Forum und Gästebuch gelandet sind. Und diese Einträge waren vorwiegend mit pornografischem Inhalt, wodurch ich mir noch evtl. rechtliche Schwierigkeiten einhandeln könnte.
Was genau ist nun ein Captcha? Wer es genau wissen will, der liest am Besten den folgenden Artikel bei Wikipedia: Captcha - Wikipedia
Im unserem Beispiel geht es darum unterscheiden zu können, ob die HTTP-Anfrage automatisch erzeugt wurde od. doch ein Mensch die Formulardaten unserers Webformulars ausgefüllt hat.
Dies erreichen wir in unserem Fall durch ein Bild, welches eine zufällige Folge von Zeichen darstellt. Für einfache Bots reicht die normale Darstellung der Zeichenfolge schon aus, aber leider ist das nur zu oft nicht der Fall. Deshalb verfolgt man bisher das Prinzip, die Zeichenfolge für OCR-Software (Software, mit der zB Ausdrucke wieder in ein Textverarbeitungsprogramm geladen werden kann) möglichst unkenntlich zu machen und für das menschliche Auge möglichst lesbar zu halten.
Einige Beispiele zu guten und schlechten Captchas findet ihr unter folgendem Link (Englisch):
PWNtcha - captcha decoder
Dort bekommt ihr Eindrücke was nun eigentlich gemeint ist und woran sich ein guter Captcha von einem schlechten unterscheidet. Aber genug der Theorie, kommen wir zur Praxis.
Folgende Dinge werden für das Tutorial vorrausgesetzt:
* verschiedene TTF-Fonts (True Type Fonts)
* eine PHP Installation mit
* GD-Bibliothek (Version 2 od. höher)
* FreeType-Bibliothek
Ob diese Bibliotheken installiert sind, könnt ihr mit der phpinfo Funktion ermitteln. Einfach diese Funktion in einem PHP-Skript aufrufen und unter dem Punkt "GD" nachlesen. Bei mir sehen die Angaben wie folgt aus (wichtige Angaben fettgedruckt):
GD Support: enabled
GD Version: bundled (2.0.28 compatible)
FreeType Support: enabled
FreeType Linkage: with freetype
FreeType Version: 2.1.9
T1Lib Support: enabled
GIF Read Support: enabled
GIF Create Support: enabled
JPG Support: enabled
PNG Support: enabled
WBMP Support: enabled
XBM Support: enabled
Weitere Informationen zur GD-Bibliothek findet ihr hier: PHP: Grafik-Funktionen - Manual
Nun zu den TrueTypeFonts (TTF). Ich benutze nämlich in meinem Beispiel keine komplizierten Bildmanipulations-Algorithmen um die Zeichenfolgen für OCR-Software unkenntlich zu machen, sondern bediene mich an einer Fülle von abstrakten und frei verfügbaren Schriftarten. Hierzu empfielt es sich zu Googeln (zB Free Fonts) und in meinem Beispiel habe ich Fonts von der Seite Fontasy.de - 1600 fonts for free! verwendet. Es empfiehlt sich Schriftarten zu nehmen, welche für das menschliche Auge gut lesbar sind und für Computer zu abstrakt wirken, um sie zu erkennen.
Beginnen wir nun gleich beim schwierigen Teil, dem Captcha. Ich erstelle hierzu eine neue leere PHP Datei und nenne sie "captcha.php", an den Anfang dieser Datei setzten wir einige Konstanten:
PHP Code:
<?php
$CAPTCHA_LENGTH = 5; // Länge der Captcha-Zeichenfolge, hier fünf Zeichen
$FONT_SIZE = 18; // Schriftgröße der Zeichen in Punkt
$IMG_WIDTH = 170; // Breite des Bild-Captchas in Pixel
$IMG_HEIGHT = 60; // Höhe des Bild-Captchas in Pixel
// Liste aller verwendeten Fonts
$FONTS[] = './ttf/kbyteff';
$FONTS[] = './ttf/actionjf'f;
$FONTS[] = './ttf/minyaf'f;
// Unser Zeichenalphabet
$ALPHABET = array('A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'Q', 'J', 'K', 'L', 'M', 'N',
'P', 'R', 'S', 'T', 'U', 'V', 'Y',
'W', '2', '3', '4', '5', '6', '7');
?>
Bisher führt unser PHP-Skript noch keine Funktion aus, sondern belegt lediglich Speicherplatz in Form von Variablen. Fügen wir nun die erste Zeile hinzu, welche wirklich etwas bewirkt:
PHP Code:
header('Content-Type: image/jpeg', true);
Führen Sie das PHP Skript einmal mit und einmal ohne diese Zeile aus. Sie werden beim Ausführen ohne diese zusätzliche Zeile keine Ausgabe erhalten. Führen sie das Skript mit dieser zusätzlichen Zeile aus, werden sie je nach Browser einen Platzhalter für ein fehlendes Bild zu sehen bekommen, im Internet Explorer ist es das typische rote "x" [img]error[/img].
Warum ist das so?
Standardmäßig liefert PHP HTML-Seiten zurück, diese sind vom MIME-Typ "text/html" od. andere. Wir möchten aber kein HTML-Dokument darstellen, sondern ein Bild und deshalb verwenden wir den MIME-Typ "image/jpeg". Somit weiß der Browser, dass er es hier mit einem Bild zu tun hat, das er anzeigen soll. Leider hat er aber noch nichts zum Anzeigen und liefert uns einen solchen Platzhalter, man kennt sie aus falsch verlinkten Bildern.
Weitere Informationen zur header-Funktion findet ihr hier: PHP: header - Manual
Wichtig ist, dass keine Ausgabe vor dem Aufruf dieser Funktion erfolgen darf (genau wie bei session_start()).
So, da dieser Platzhalter recht unschön wirkt, erzeugen wir nun unser eigentliches Bild mit folgenden Zeilen:
PHP Code:
$img = imagecreatetruecolor($IMG_WIDTH, $IMG_HEIGHT);
imagejpeg($img);
imagedestroy($img);
PHP Code:
resource imagecreatetruecolor ( int x_size, int y_size )
Die Funktion erwartet zwei ganzzahlige Parameter vom Typ "int" und liefert als Ergebnis einen Zeiger. Dieser Zeiger zeigt auf ein leeres Bild (schwarzer Hintergrund) mit der Breite "x_size" und der Höhe "y_size" in Pixeln.
Die zweite Funktion "imagejpeg" ist wie folgt definiert:
PHP Code:
int imagejpeg ( resource im [, string filename [, int quality]] )
Die Funktion erwartet einen Pflichtparameter vom Typ "resource". Gemeint ist also das zuvor von uns mit imagecreatetruecolor erzeugte Zeigerobjekt auf ein Bild. Der zweite und dritte Parameter ist optional, das heißt er muss nicht angegeben werden.
Als zweiten Parameter können sie einen Dateinamen angeben. So würde zB imagejpeg($img, 'captcha.jpg') das Bild nicht an den Browser zurückliefern, sondern im aktuellen Verzeichnis des Webservers speichern. Da unser Bild aber bei jedem Aufruf neu generiert wird, ist es sinnlos das Bild zu speichern - wir liefern es Direkt an den Browser zurück.
Der dritte Parameter gibt die JPEG-Qualität des Bildes an. Wie sie vielleicht wissen verfügt das JPEG-Bildformat über 0 bis 100 Qualitätsstufen, wobei 100 der besten und 0 der schlechtesten Qualität entspricht. Wollen sie diesen Parameter verwenden, aber das Bild NICHT speichern sondern direkt an den Browser zurückgeben, lassen sie den zweiten Parameter für filename einfach leer. Das bedeutet also der Aufruf von
PHP Code:
imagejpeg($img, '', 50)
liefert das Bild in durchschnittlicher Qualität an den Browser zurück. Für uns ist dieser Parameter aber uninteressant, für Webseiten mit hohem Traffic wäre es jedoch zu überlegen, einen geeigneten Wert selbst zu definieren. Dabei sollte aber darauf geachtet werden, dass der Text lesbar bleibt.
Die letzte Funktion "imagedestroy" erwartet wieder einen Zeiger auf ein Bild als Parameter und löscht das Bild aus dem Speicher des Webservers. In der PHP-Dokumentation wurd die Funktion wie folgt beschrieben:
PHP Code:
int imagedestroy ( resource im )
mageDestroy() gibt den durch das Bild im belegten Speicher wieder frei. Im ist der Bezeichner, der ihnen beim Aufruf der Funktion ImageCreate() zurück gegeben worden ist.
Wenn sie das PHP-Skript nun ausführen, sollten sie schon ein schwarzes Bild in der von uns mit IMG_WIDTH und IMG_HEIGHT definierten Größe sehen. Nicht sehr aufregen, oder? Verleien wir dem ganzen also ein bisschen Farbe, fügen Sie folgende Anweisung unter der imagecreate Funktion ein:
PHP Code:
$col = imagecolorallocate($img, 255, 0, 0);
imagefill($img, 0, 0, $col);
Die Funktion "imagecolorallocate" bestimmt einen Farbwert und erwartet vier Parameter. Der erste Parameter ist wie üblich unser Zeiger auf das Bild, die restlichen drei Parameter repräsentieren jeweils den Rot, Grün, Blau Anteil der Farbe (RGB). Wobei jeder Farbwert nur 2^8 (8 Bit) Mögliche Zustände haben kann, also von 0 bis 255. Hier einige Beispiele:
PHP Code:
// R = Rot
// G = Grün
// B = Blau
// Weiss: R = 255, G = 255, B = 255
$white = imagecolorallocate($img, 255, 255, 255);
// Schwarz: R = 0, G = 0, B = 0
$black = imagecolorallocate($img, 0, 0, 0);
// (Hell-)Grün: R = 0, G = 255, B = 0
$green = imagecolorallocate($img, 0, 255, 0);
// (Dunkel-)Grün: R = 0, G = 100, B = 0
$green = imagecolorallocate($img, 0, 100, 0);
In unserem Beispiel habe ich den Rotwert auf den höchst möglichen Wert (255) und den Grün und Blau Anteil auf 0 gesetzt. Wir erhalten also, das für den Computer "roteste" Rot. Andere Farbtöne erhalten wir nur durch die Mischung dieser drei Grundfarben.
Ohne die zweite Zeile
PHP Code:
imagefill($img, 0, 0, $col);
sehen wir aber gar nichts.
Diese Funktion erwartet wieder unseren Parameter $img als Zeiger auf unser Bild, eine x und y Koordinate und einen Farbwert, der zuvor von uns mit "imagecolorallocate" erzeugt wurde. Die Funktion füllt dann eine Fläche von der Koordinate x, y beginnend, mit der angegebenen Farbe aus, bis zum Ende des Bildes, also die rechte untere Ecke (x = IMG_WIDTH, y = IM_HEIGHT).
Führen wir das Skript nun aus, erhalten wir eine farbige (rote) Fläche. Besser als schwarz ja, aber leider nicht viel. Deshalb gestalten wir das Ganze interessanter und erzeugen einen dynamischen Hintergrund aus zufälligen Werten. Hierzu ändern wir die Zeile
PHP Code:
$col = imagecolorallocate($img, 255, 0, 0);
PHP Code:
$col = imagecolorallocate($img, rand(200, 255), rand(200, 255), rand(200, 255));
Ich fasse hier nocheinmal die bisherigen Codezeilen zusammen:
PHP Code:
<?php
$CAPTCHA_LENGTH = 5; // Länge der Captcha-Zeichenfolge, hier fünf Zeichen
$FONT_SIZE = 18; // Schriftgröße der Zeichen in Punkt
$IMG_WIDTH = 170; // Breite des Bild-Captchas in Pixel
$IMG_HEIGHT = 60; // Höhe des Bild-Captchas in Pixel
// Liste aller verwendeten Fonts
$FONTS[] = './ttf/kbyteff';
$FONTS[] = './ttf/actionjff';
$FONTS[] = './ttf/minyaff';
// Unser Zeichenalphabet
$ALPHABET = array('A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'Q', 'J', 'K', 'L', 'M', 'N',
'P', 'R', 'S', 'T', 'U', 'V', 'Y',
'W', '2', '3', '4', '5', '6', '7');
// Wir teilen dem Browser mit, dass er es hier mit einem JPEG-Bild zu tun hat.
header('Content-Type: image/jpeg', true);
// Wir erzeugen ein leeres JPEG-Bild von der Breite IMG_WIDTH und Höhe IMG_HEIGHT
$img = imagecreatetruecolor($IMG_WIDTH, $IMG_HEIGHT);
// Wir definieren eine Farbe mit Zufallszahlen
// Die Farbwerte sind durchgehend und absichtlich hoch (200 - 256) gewählt,
// um eine "leichte" Farbe zu erhalten
$col = imagecolorallocate($img, rand(200, 255), rand(200, 255), rand(200, 255));
// Wir füllen das komplette Bild der zuvor definierten Farbe
imagefill($img, 0, 0, $col);
imagejpeg($img);
imagedestroy($img);
?>
Der nächste Schritt ist das erstellen und Ausgeben der Zeichenfolge. Wir definieren uns hierzu zuerst drei Variablen x und y für die Koordinaten des einzelnen Zeichens und eine vorerst leere Zeichenkette für unseren Captcha-Code. Dann erstellen wir eine for-Schleife von 0 bis CAPTCHA_LENGTH (Länge der Zeichenfolge) und erhöhen sie bei jedem Durchlauf um 1. In einem Durchlauf werden dann folgende Schritte ausgeführt:
* Zuerst wird ein zufälliges Zeichen aus dem vorgegeben Alphabet ermittelt.
* Dieses Zeichen wird an die Zeichenkette $captcha angehängt.
* Es folgt eine uns bekannte Programmzeile, nämlich eine zufällige Farbdefinition mit rand und imagecolorallocate.
* Nun wählen wir aus unserer Liste von Fonts wieder mit rand einen zufälligen Schriftfont aus.
* Anschließend wird die y-Koordinate ($y) mit einem Grundabstand plus einem zufälligen Wert definiert.
* Wir definieren eine Variable angle (=Winkel) und weisen ihr wieder einen zufälligen Wert zu.
* Mithilfe der Funktion imagettftext schreiben wir nun, mit den zuvor definierten Parametern, das Zeichen in das Bild.
* Die Funktion imagettfbbox liefert uns die Ausmaße des Zeichens in einem Array.
* Aufgrund des Ergebnisses von imagettfbbox wird ein Mindestabstand zum nächsten Zeichen ermittelt.
So sieht das Ganze bei mir aus:
PHP Code:
:
$captcha = ''; // Enthält später den Captcha-Code als String
$x = 10; // x-Koordinate des ersten Zeichens, 10 px vom linken Rand
for($i = 0; $i < $CAPTCHA_LENGTH; $i++) {
$chr = $ALPHABET[rand(0, count($ALPHABET) - 1)]; // ein zufälliges Zeichen aus dem definierten Alphabet ermitteln
$captcha .= $chr; // Der Zeichenfolge $captcha das ermittelte Zeichen anfügen
$col = imagecolorallocate($img, rand(0, 199), rand(0, 199), rand(0, 199)); // einen zufälligen Farbwert definieren
$font = $FONTS[rand(0, count($FONTS) - 1)]; // einen zufälligen Font aus der Fontliste FONTS auswählen
$y = 25 + rand(0, 20); // die y-Koordinate mit einem Mindestabstand plus einem zufälligen Wert festlegen
$angle = rand(0, 30); // ein zufälliger Winkel zwischen 0 und 30 Grad
/*
* Diese Funktion zeichnet die Zeichenkette mit den
* gegeben Parametern (Schriftgröße, Winkel, Farbe, TTF-Font, usw.)
* in das Bild.
*/
imagettftext($img, $FONT_SIZE, $angle, $x, $y, $col, $font, $chr);
$dim = imagettfbbox($FONT_SIZE, $angle, $font, $chr); // ermittelt den Platzverbrauch des Zeichens
$x += $dim[4] + abs($dim[6]) + 10; // Versucht aus den zuvor ermittelten Werten einen geeigneten Zeichenabstand zu ermitteln
}
Die nachfolgende Zeile, in welcher wieder ein zufälliger Farbwert ermittelt wird, hatten wir in ähnlicher Form schon bei der Definition der Hintergrundfarbe. Die besonderheit liegt darin, dass diesmal hauptsächlich der untere Farbwert ausgewählt wird, um einen stärkeren Kontrast zu erhalten. Des weiteren vermeide ich mit den Werten 199 statt 200 den schlimmsten aller Fälle, dass die Hintergrundfarbe und die Schriftfarbe ident sind.
PHP Code:
$col = imagecolorallocate($img, rand(0, 199), rand(0, 199), rand(0, 199));
[php] $y = 25 + rand(0, 20);]/PHP]
Diese Zeile gibt die y-Position des Zeichens an. Ich verwende hier aus optischen Gründen einen vorgegebenen Wert von 25 und addiere noch einen zufälligen Wert, um die Zeichen nicht in einer Linie darzustellen. Das menschliche Auge hat damit keine großen Probleme, dafür evtl. die OCR-Software.
Mit $angle gebe ich einen Winkel zwischen 0-30 Grad an. Diese Angabe könnte noch verbessert werden, indem man den Bereich von 330 - 359 Grad ebenfalls zulässt. Die Funktion erwartet einen ganzzahligen int-Wert zwischen 0 und 359.
Die Funktion imagettftext zeichnet schlussendlich das Zeichen in das Bild. Die Funktion erwartet den Zeiger auf das Bild, eine Schriftgröße, einen Winkel, x-Koordinate, y-Koordinate, Farbe, TTF-Font und Text. Text ist in unserem Fall nur ein Zeichen, da wir jedes Zeichen anderwertig darstellen wollen, es ist aber durchaus üblich ganze Zeichenketten zu zeichnen.
Die Funktion imagettfbbox (bb = Bounding Box) liefert ein Array mit verschiedenen Koordinaten, welche die vier Ecken des Zeichens darstellen (zB x links oben, y links oben, x rechts unten, y rechts unten, usw.) Leider ist diese Funktion schwer nachzuvollziehen und es ist bekannt, dass die Funktion nicht mit Winkelwerten zurecht kommt. Das bedeutet, der genaue Abstand kann bei einem Winkel != 0 nicht korrekt ermittelt werden. Deshalb empfehle ich einen möglichst großen mindestabstand zu definieren (letzte Zeile x + Abstand).
Die benötigte Höhe kann ebenfalls nicht genau ermittelt werden und deshalb sollten sie das Bild am Anfang mit großzügigen Werten definieren (IMG_WIDTH, IMG_HEIGHT). Sie werden auch in diesem Beispiel erkennen, dass es in seltenen Fällen vorkommt, dass die Zeichen unleserlich über den Rand dargestellt werden.
In der letzten Zeile versuche ich einen ausreichenden Abstand der Zeichen zu ermitteln, so dass sich die Zeichen nicht überschneiden.
Hier nocheinmal der komplette Code, um einen einfachen Captcha darzustellen. Nach Ablauf des Skript enthält die Variable $captcha den Code als Zeichenkette. Mit diesem kann dann beliebig weiterverfahrt werden. Hierzu zählt zB das Abspeichern in einer Datenbank od. Datei inkl. eines Schlüssels, um den richtigen Benutzer zuzuordnen.
PHP Code:
<?php
$CAPTCHA_LENGTH = 5; // Länge der Captcha-Zeichenfolge, hier fünf Zeichen
$FONT_SIZE = 18; // Schriftgröße der Zeichen in Punkt
$IMG_WIDTH = 170; // Breite des Bild-Captchas in Pixel
$IMG_HEIGHT = 60; // Höhe des Bild-Captchas in Pixel
// Liste aller verwendeten Fonts
$FONTS[] = './ttf/kbyte.\ttf';
$FONTS[] = './ttf/actionj.\ttf';
$FONTS[] = './ttf/minya.\ttf';
// Unser Zeichenalphabet
$ALPHABET = array('A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'Q', 'J', 'K', 'L', 'M', 'N',
'P', 'R', 'S', 'T', 'U', 'V', 'Y',
'W', '2', '3', '4', '5', '6', '7');
// Wir teilen dem Browser mit, dass er es hier mit einem JPEG-Bild zu tun hat.
header('Content-Type: image/jpeg', true);
// Wir erzeugen ein leeres JPEG-Bild von der Breite IMG_WIDTH und Höhe IMG_HEIGHT
$img = imagecreatetruecolor($IMG_WIDTH, $IMG_HEIGHT);
// Wir definieren eine Farbe mit Zufallszahlen
// Die Farbwerte sind durchgehend und absichtlich hoch (200 - 256) gewählt,
// um eine "leichte" Farbe zu erhalten
$col = imagecolorallocate($img, rand(200, 255), rand(200, 255), rand(200, 255));
// Wir füllen das komplette Bild mit der zuvor definierten Farbe
imagefill($img, 0, 0, $col);
$captcha = ''; // Enthält später den Captcha-Code als String
$x = 10; // x-Koordinate des ersten Zeichens, 10 px vom linken Rand
for($i = 0; $i < $CAPTCHA_LENGTH; $i++) {
$chr = $ALPHABET[rand(0, count($ALPHABET) - 1)]; // ein zufälliges Zeichen aus dem definierten Alphabet ermitteln
$captcha .= $chr; // Der Zeichenfolge $captcha das ermittelte Zeichen anfügen
$col = imagecolorallocate($img, rand(0, 199), rand(0, 199), rand(0, 199)); // einen zufälligen Farbwert definieren
$font = $FONTS[rand(0, count($FONTS) - 1)]; // einen zufälligen Font aus der Fontliste FONTS auswählen
$y = 25 + rand(0, 20); // die y-Koordinate mit einem Mindestabstand plus einem zufälligen Wert festlegen
$angle = rand(0, 30); // ein zufälliger Winkel zwischen 0 und 30 Grad
/*
* Diese Funktion zeichnet die Zeichenkette mit den
* gegeben Parametern (Schriftgröße, Winkel, Farbe, TTF-Font, usw.)
* in das Bild.
*/
imagettftext($img, $FONT_SIZE, $angle, $x, $y, $col, $font, $chr);
$dim = imagettfbbox($FONT_SIZE, $angle, $font, $chr); // ermittelt den Platzverbrauch des Zeichens
$x += $dim[4] + abs($dim[6]) + 10; // Versucht aus den zuvor ermittelten Werten einen geeigneten Zeichenabstand zu ermitteln
}
imagejpeg($img); // Ausgabe des Bildes an den Browser
imagedestroy($img); // Freigeben von Speicher
?>
Inzwischen könnt ihr euch die Image-Funktionen der GD-Bibliothek genauer ansehen: PHP: Grafik-Funktionen - Manual
und mit dem bestehenden Code experimentieren. Spielt euch zB mit Farbwerten und versucht noch weitere, verwirrende Objekte in den Hintergrund zu zeichen (zB Linien, Kreise, etc.).
Ich hoffe euch hat das Tutorial bisher gefallen und könnt mir ein Feedback geben.
Quelle:

Falls es dies gab,bitte löschen






