Créer un antispam Captcha

La plus part des sites existant permet une interaction avec l’utilisateur soit en proposant des inscriptions à leurs services, des publications des commentaires, des articles, des questions, … via des formulaires contenant un champ anti-spam permettant de lutter contre les robots spammeurs.

Il existe plusieurs techniques permettant ce mécanisme, la plus utilisée est captcha pour Completely Automated Public Turing Test To Tell Computers and Humans Apart. Il existe de nombreuses implémentations de captcha telles que la visualisation d’images et les tests audio. Dans cet article on va essayer d’implémenter un exemple d’anti-spam avec PHP en se basant sur la visualisation d’image.

Avant de commencer il est très important d’activer la bibliothèque GD2 sur le serveur apache. Pour réaliser cet exemple on aura besoin des images qui serviront comme fond (il est également possible de créer une image au lieu d’utiliser une existante) et des polices qui seront utilisés pour écrire sur l’image. Pour mieux cerner les choses, on créera l’arborescence suivantes sur le répertoire www d’apache (pour mon cas /var/www/) :

  • antispam/index.php
  • antispam/antispam.php
  • antispam/captchabgs/*.png
  • antispam/fonts/*.otf
  • antispam/images/refresh.png

Le répertoire « captchabgs » contient les images (PNG) qui serviront comme background de l’image captcha. Les polices utilisées dans cet exemple sont gratuites et sous licence GPL téléchargeables depuis kawoszeh et rawengulk. On aura également besoin d’une image pour créer une action permettant de recharger le code captcha s’il n’est pas visible.

Le principe illustré dans cet exemple est simple :

  • génération aléatoire d’un code captcha à partir des caractères alphanumérique. Après Cryptage avec MD5, ce code sera stocker dans la session de l’application pour vérification ultérieure.
  • A partir de l’image arrière plan, choisie aléatoirement, on va générer une nouvelle image sur laquelle le code captcha sera écrit. Dans cette partie on fera appel à la bibliothèque GD2 et à la police utilisée. Je note que dans notre exemple chaque caractère peut être écrit sur l’image avec une police différente.
  • Lors de la validation du formulaire, le code saisie par l’utilisateur (après cryptage avec MD5) sera comparé au code captcha stocké dans la session.

La page antispam.php
Commençant par antispam.php, cette page a pour rôle de générer le code et l’image captcha. Comme je l’ai signaler on aura besoin d’utiliser les variables de session, il faut donc démarrer la session :

On aura besoin de définir le chemin absolut pour récupérer les images et les polices:

if ( !defined(‘ABSPATH’) ) define(‘ABSPATH’, dirname(__FILE__) . ‘/’) ;

Le code captcha sera généré aléatoirement à bases des caractères alphanumérique, pour cela on va écrire une fonction permettant de générer ce code :

function generateCaptchaCode($length) {
  $chars = ’123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstvwxyz’;
  $captcha_code =  »;
  for ($i=0; $i<$length; $i++) {
    $captcha_code .= $chars{ mt_rand( 0, strlen($chars)-1 ) };
  }
  return $captcha_code;
}

On récupère l’image qui sera utilisé comme backgroud, pour cela on fera appel à la fonction glob de PHP qui permet de rechercher les chemins qui vérifient un masque.

$captchabgs = glob(‘captchabgs/*.png’);

L’image backgroud sera choisi aléatoirement depuis le tableau $captchabgs pour cela on va faire appel à la fonction array_rand qui va nous retourner un élément du tableau $captchabgs :

$captchabg = $captchabgs[array_rand($captchabgs)] ;

Idem au images, on va récupérer la liste des chemins des polices qu’on peut utilisé :

$captchafonts = glob(‘fonts/*.otf’) ;

Pour générer le code captcha, le crypter avec MD5 et le stocker dans une variable session on aura besoin du code suivant :

$captcha = generateCaptchaCode(7) ;
$_SESSION[‘sysCaptchaCode’] = md5($captcha);

La première opération offerte par GD2 qu’on va utiliser est imagecreatefrompng qui va nous permettre de créer une image (en mémoire) à partir de l’image background $captchabg :

$captchaimg = imagecreatefrompng($captchabg) ;

Jusqu’à maintenant, on a pu générer le code captcha, récupérer la liste des polices qu’on va utiliser et de créer l’image background. Il reste l’essentiel du travail qui est l’écriture du code captcha sur l’image, pour le réaliser on aura besoin de définir la liste des couleurs qu’on peut appliquer à un caractère du code lors de son écriture sur l’image ainsi que son inclinaison.

Pour définir les colleurs, on va faire appel à la fonction imagecolorallocate qui permet de définir une couleur à base du RVB et l’associé à une image. Chaque caractère du code captcha peut être écrit avec une couleur différente, on va donc définir une liste de couleurs :

$colors=array (imagecolorallocate($captchaimg, 81,81,172),
                imagecolorallocate($captchaimg,  72,72,75),
                imagecolorallocate($captchaimg, 143,143,212),
                imagecolorallocate($captchaimg, 168,168,249),
                imagecolorallocate($captchaimg, 120,122,135),
                imagecolorallocate($captchaimg, 40,59,80),
                imagecolorallocate($captchaimg, 204,225,249) ) ;

Pour les inclinaisons, chaque caractère du code captcha aura sa propre inclinaison, on va définir donc un tableau d’inclinaisons possibles :

$inclinaison = array ( mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 )) ;

Pour éditer l’image, GD2 nous offre l’opération imagettftext(image, taille police, inclinaison, coordonnée X, coordonnée Y, couleur, police, texte). Cette opération permet d’écrire du text sur une image, elle aura besoin de l’image bien sur, la taille de police, l’inclinaison, les coordonnées X et Y à partir des quelles le texte sera écrit, la couleur et la police. Imagettftext supporte également des polices au format otf.

for ($i = 0; $i < 7; $i++) {
imagettftext($captchaimg, 36, $inclinaison[$i], (30*$i) +15, 50, $colors[array_rand($colors)], ABSPATH.$captchafonts[array_rand($captchafonts)], $captcha[$i]);
}

La page antispam.php va retourner une image, alors il faut définir son entête :

header(‘Content-Type: image/png’);

L’image créée doit être renvoyée, pour cela il faut juste utiliser l’opération imagepng :

imagepng($captchaimg);

Le travail est terminé :D sauf qu’il faut préserver la mémoire et donc détruire l’image en mémoire ;) :

imagedestroy($captchaimg);

Le code de la page antispam.php est le suivant :

<?php
//Démarrage de la session
session_start();
//Définition du chemin absolut
if ( !defined(‘ABSPATH’) ) define(‘ABSPATH’, dirname(__FILE__) . ‘/’);
/*
fonction qui génère un captcha code.
elle prend en paramètre la taille du code captcha
*/

function generateCaptchaCode($length) {
  $chars = ’123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstvwxyz’;
  $captcha_code =  »;
  for ($i=0; $i<$length; $i++) {
    $captcha_code .= $chars{ mt_rand( 0, strlen($chars)-1 ) };
  }
  return $captcha_code;
}
/*
Récupération de l’image background
glob va récupérer les chemins des images avec extension png
*/

$captchabgs = glob(‘captchabgs/*.png’);

//choix aléatoire de l’images background
$captchabg = $captchabgs[array_rand($captchabgs)];
/*
On récupère la liste des polices qu’on va utilisé
*/

$captchafonts = glob(‘fonts/*.otf’);
//génération du code captcha
$captcha = generateCaptchaCode(7);
//stocker le code captcha crypté en session
$_SESSION[‘sysCaptchaCode’] = md5($captcha);
/*
création de l’image background à partir du chemin récupéré
en utilisant imagecreatefrompng. Cette image sera éditer
et renvoyer comme image captcha
*/

$captchaimg = imagecreatefrompng($captchabg);
/*
Définition des couleurs pour les caractères.
imagecolorallocate permet de définir une couleur à base du RVB et l’associé à une image créée.
*/

$colors=array ( imagecolorallocate($captchaimg, 81,81,172),
                imagecolorallocate($captchaimg,  72,72,75),
                imagecolorallocate($captchaimg, 143,143,212),
                imagecolorallocate($captchaimg, 168,168,249),
                imagecolorallocate($captchaimg, 120,122,135),
                imagecolorallocate($captchaimg, 40,59,80),
                imagecolorallocate($captchaimg, 204,225,249) );
/*
le text sera incliné sur l’image captcha, on va donc définir un tableau
des inclinaisons
*/

$inclinaison = array ( mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ),
                        mt_rand( -25, 25 ));
/*
génération de l’image captcha en écrivant du texte sur l’image background en utilisant imagettftext(image, taille police, inclinaison, coordonnée X, coordonnée Y, couleur, police, texte)
*/

for ($i = 0; $i < 7; $i++) {
imagettftext($captchaimg, 36, $inclinaison[$i], (30*$i) +15, 50, $colors[array_rand($colors)], ABSPATH.$captchafonts[array_rand($captchafonts)], $captcha[$i]);
}
//Définition de l’en-tête HTTP de la page.
header(‘Content-Type: image/png’);
//Renvoie de l’image captcha
imagepng($captchaimg);
//Destruction de l’image en mémoire
imagedestroy($captchaimg);
?>

L’exécution de cette page dans un navigateur affiche une image du genre :

La page index.php
La page index.php contient le formulaire permettant d’écrire le code captcha et le renvoyer. Elle ne contient pas des traitement spécieux à part le contrôle de légalité du code captcha saisi et le code généré. Le formulaire utilisé est du genre :

Le code complet de la page index.php est le suivant :

<?php
/* Démarrage de la session */
session_start();
?>
<html>
<head>
<title>Anti-Spam avec Captcha</title>
</head>
<body>
<div>
  <?php
  /* Si l’utilisateur a bien saisie le code Captcha */
if (isset($_REQUEST[‘userCaptchaCode’])) {
  if (!empty($_REQUEST[‘userCaptchaCode’]))
  {
    $userCaptchaCode = $_REQUEST[‘userCaptchaCode’];
    /* Cryptage avec MD5 du code saisie par l’utilisateur et contrôle avec le code enregistré dans la session */
    if( md5($userCaptchaCode) == $_SESSION[‘sysCaptchaCode’] )
      echo ‘<h2 class="correct">Correct !</h2>’; // Le code est correcte
    else echo ‘<h2 class="incorrect">Ahem.. Recommencez !</h2>’; // Le code est incorrecte
  }
else
echo ‘<h2 class="correct">Vous  &ecirc;tes un Spam !</h2>’;
}
  ?>
 
  <!– le traitement du formulaire se fait sur la même page index.php –>
  <form method="post" action="<?php echo $_SERVER[‘PHP_SELF’]; ?>" name="captchaForm">
  <div name ="captchaDiv">
    <!– Notre image captcha créée depuis antispam.php –>
    <img src="antispam.php" alt="Captcha" id="captcha" name="captcha"/>
    <!– un peu de javascript pour recherger l’image si le code captcha est illisible  –>
    <a style="cursor:pointer" onclick="document.images.captcha.src=’antispam.php?id=’
+Math.round(Math.random(0)*1000)+1">
<img src="<?php echo dirname($_SERVER["PHP_SELF"]).‘/images/refresh.png’;?>" alt="recharger l’image"/></a>
    <p>
      <label for="userCaptchaCode">Entrez le code</label>
      <input name="userCaptchaCode" id="userCaptchaCode" type="text" />
      <input type="submit" name="submit" id="submit" value="Valider" />
    </p>
  </div>
  </form>
</div>
</body>
</html>

Notre exemple est terminé, j’espère que je vous apporté de la nouveauté pour les codes captcha. Vous pouvez Télécharger le code source et lui apporter votre amélioration.

A propos de l'auteur

Faut il vraiment porter des lunettes, pour travailler la nuit, sur un terminal vert sur noire, pour être Geek pour devenir root ?