Captcha en PHP

Captcha o CAPTCHA son las siglas en inglés de Completely Automated Public Turing test to tell Computers and Humans Apart, o lo que es lo mismo: Prueba de Turing completamente automática y pública para diferenciar computadoras de humanos [+ info en wikipedia].

La validación de formularios para evitar a los bots es algo fundamental si no quieres recibir cantidades ingentes de correo basura. Hay múltiples opciones gratuitas como reCaptacha de Google, Securimage en PHP, o JCaptcha en Java, pero aqui lo vamos a hacer de manera artesanal con solo unas cuantas lineas de PHP y algunas de sus funciones.

En este artículo vamos a trabajar con dos archivos diferentes:

  • demo-captcha.php ========> que crea la imagen en png.
  • demo-captcha-form.php ===> que realiza la validación.

Vamos a ello…

Ver Demo

El Captcha

demo-captcha.php

Este archivo es el encargado de crear el captcha en formato image/png. Tambien crea la variable de sesión $_SESSION['captchaKey'] y le asigna el valor que hemos pintado en la imagen del Captcha para validarlo en el formulario, como veremos más adelante.

De momento vamos a crear el captcha en PHP.

Veamos el código por partes

Iniciamos sesión:

session_start();

Definimos el número de caracteres que tendrá el captcha:

$captchaTextSize = 5;

Con un bucle do y mientras se cumpla la condición dentro del while :

  • Creamos la variable $md5Hash con la funcion microtime() que devuelve el timestamp actual con microsegundos y la funcion mktime() que devuelve el timestamp de una fecha. Se multiplican ambos resultados y se codifican en md5 con la función md5(). El resultado es algo así: bdffe7ebff923061748cb436ab2501b0.
  • Creamos el array $xChar para excluir ciertos caracteres que pueden crear confusion:
    0 – O – o – 1 – l
  • Eliminamos los caracteres excluidos remplazándolos por nada.
do {
    $md5Hash = md5(microtime()*mktime());
    $xChar = array("0", "1", "o", "O", "l");
    $md5Hash = str_replace($xChar, "", $md5Hash);
} while(strlen($md5Hash)<$captchaTextSize);

A continuación creamos la variable $captchaKey con la cadena $md5Hash acortándola con la función substr() al tamaño definido en $captchaTextSize :

$captchaKey = substr($md5Hash, 0, $captchaTextSize);

Creamos una variable de sesión codificando $captchaKey en md5 y guardamos ahí el codigo que vamos a escribir en el captcha para disponer de él en el formulario:

$_SESSION['captchaKey'] = md5($captchaKey);

Seleccionamos una imagen (que debe existir previamente en su directorio) para crear el captcha, algo así como la imagen de plantilla para el captcha final, usaremos la función imagecreatefrompng() pasándole una url de una imagen (recordar que debe existir previamente):

$captchaImage = imagecreatefrompng("../img/bg_captcha.png");

Definimos un color RGB para el texto de los caracteres con imagecolorallocate() :

$textColor = imagecolorallocate($captchaImage, 80, 80, 80);

También crearemos unas líneas aleatorias para dificultar la comprensión del captcha sin que resulte excesivo para los humanos.
Definimos el color de las líneas aleatorias (yo le pongo el mismo color que el texto) :

$lineColor = imagecolorallocate($captchaImg, 80, 80, 80);

Definimos el ancho de las lineas aleatorias con la función imagesetthickness() :

imagesetthickness($captchaImage, 1);

Definimos el tamaño de la imagen con getimagesize() pasándole la url de una imagen existente:

$imageInfo = getimagesize("../img/bg_captcha.png");

Definimos el numero de lineas aleatorias con la funcion rand() de PHP:

$linesToDraw = rand(5,8);

Pintamos las lineas aleatorias con un bucle for. Para cada linea :

  • Definimos un punto inicial $xStart
  • Definimos un punto final $xEnd
  • Pintamos la linea en la imagen con la función imageline() que tiene 6 argumentos y son: imagen , posicion x inicial , posicion y inicial , posicion x final , posicion y final y color de linea .
for($i=0; $i<$linesToDraw; $i++){
  $xStart = mt_rand(0, $imageInfo[0]);
  $xEnd = mt_rand(0, $imageInfo[0]);
  imageline($captchaImage, $xStart, 0, $xEnd, $imageInfo[1], $lineColor);
}

Pintamos el código captcha en la imagen con la función imagettftext() de PHP. Los argumentos de esta función son 8: imagen , tamaño , ángulo , posicion x , posicion y , color del texto , url de la fuente de texto y el texto a escribir.

imagettftext($captchaImage, 20, 0, 10, 30, $textColor, "../../fonts/VeraBd.ttf", $captchaKey);

Declaramos los encabezados para exportar el archivo como imagen:

header("Content-type: image/png");
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Fri, 19 Jan 1994 05:00:00 GMT");
header("Pragma: no-cache");

Y finalmente imprimimos la imagen al archivo que leeremos desde el navegador con la función imagepng() :

imagepng($captchaImage);

Y así queda el código completo del archivo demo-captcha.php, con comentarios:

demo-captcha.php
<?php /* CAPTCHA CON PHP */
 	
  //iniciamos sesion	
  session_start();
 
  //numero de caracteres para el captcha	
  $captchaTextSize = 5;
 
  //haz esto...
  do {
    //creamos la variable $md5Hash que sera una cadena aleatoria	
    $md5Hash = md5(microtime()*mktime());
    //array para excluir caracteres
    $xChar = array("0", "1", "o", "O", "l");
    //remplazar caracteres excluidos
    $md5Hash = str_replace($xChar, "", $md5Hash);
  } while(strlen($md5Hash)<$captchaTextSize); //...mientras se cumpla esto
 
  //creamos la variable $captchaKey del tamaño definido en $captchaTextSize	
  $captchaKey = substr($md5Hash, 0, $captchaTextSize);
 
  //creamos variable de sesion codificando $captchaKey 	
  $_SESSION['captchaKey'] = md5($captchaKey);
 
  //seleccionamos imagen para el captcha	
  $captchaImage = imagecreatefrompng("../img/bg_captcha.png");
 
  //color del texto
  $textColor = imagecolorallocate($captchaImage, 80, 80, 80);
 
  //color de lineas aleatorias	
  $lineColor = imagecolorallocate($captchaImage, 80, 80, 80);
 
  //ancho de lineas aleatorias
  imagesetthickness($captchaImage, 1);
 
  //tamaño de imagen
  $imageInfo = getimagesize("../img/bg_captcha.png");
	
  //numero de lineas aleatorias	
  $linesToDraw = rand(5,8);
 
  //para cada linea aleatoria...	
  for($i=0; $i<$linesToDraw; $i++){
    //punto inicial
    $xStart = mt_rand(0, $imageInfo[0]);
    //punto final
    $xEnd = mt_rand(0, $imageInfo[0]);
    //pintamos la linea
    imageline($captchaImage, $xStart, 0, $xEnd, $imageInfo[1], $lineColor);
  }
  
  //pintamos el codigo en la imagen	
  imagettftext($captchaImage, 20, 0, 10, 30, $textColor, "../../fonts/VeraBd.ttf", $captchaKey);
	
  //encabezados para enviar como imagen	
  header("Content-type: image/png");
  header("Cache-Control: no-cache, must-revalidate");
  header("Expires: Fri, 19 Jan 1994 05:00:00 GMT");
  header("Pragma: no-cache");
 
  //imagen captcha final	
  imagepng($captchaImage);
 
?>
* Se ha añadido un botón para recargar el captcha y ver como cambia con cada actualización.

Además de la imagen tenemos guardada (por detrás) la variable de sesión $_SESSION['captchaKey'] que contiene el código que hemos escrito en la imagen.

Ya tenemos la imagen y el código de validación, ahora vamos a validar el Captcha en un formulario…

El Formulario

demo-captcha-form.php

El Captcha hay que validarlo en algún sitio, para ello crearemos otro archivo que será un formulario y que en este caso solo validará el captcha que hemos creado con el primer archivo.

Este archivo consta de dos partes, una primera parte de donde realizaremos la validación y una segunda parte que será formulario.

Vamos con la parte de la validación

Iniciamos sesión:

session_start();

Definimos una variable de sesión para mostrar el mensaje:

$_SESSION['mensaText']= "";

Con un if definimos lo siguiente:

  • Si no existe el campo captcha…
    • Definimos el mensaje de inicio
  • Si existe el campo captcha y además no es igual a la variable de sesion captchaKey (la que recibimos del primer archivo)
    • Definimos el mensaje de error
  • Si las anteriores condiciones no se cumplen es porque que el captcha es correcto, entonces…
    • Definimos el mensaje de correcto
    • Borramos los campos guardados para vaciar el formulario
if ( !isset($_POST['captchaCode']) ) {
  $_SESSION['mensaText']='Escribe el codigo Captcha';  
} elseif( md5($_POST['captchaCode'])!=$_SESSION['captchaKey'] ) {
  $_SESSION['mensaText']='El Captcha no es correcto';
} else {
  $_SESSION['mensaText']='El CAPTCHA es correcto';
  unset($_POST);
}

Vamos ahora con la parte del formulario

En el formulario hay que tener en cuenta los atributos enctype="multipart/form-data" (necesario para enviar archivos) y method="POST" (para enviar los datos por HTTP) . Y dentro del formulario:

  • Pintamos el mensaje con la variable $_SESSION['mensaText'].
  • Pintamos un campo de texto para introducir el código del captcha.
  • Pintamos la imagen para el captcha y en el atributo src le ponemos la ruta del primer archivo que hemos creado, el que nos devolvía la imagen del captcha: demo-captcha.php.
  • Finalmente pintamos un boton para enviar el formulario.

Hay que recordar que junto a la imagen del captcha (por detras) recibimos una variable de sesión $_SESSION['captchaKey'] que tiene el valor del código del captcha, variable que ya hemos evaluado en la parte anterior de la validación, dentro del elseif .

<form enctype="multipart/form-data" method="POST" action="">
  <?php echo $_SESSION['mensaText']?><br>
  <input name="captchaCode" placeholder="captcha" type="text" value="" />
  <img src="./demo-captcha.php" alt="captcha" title="Código Captcha" />
  <input type="submit" name="enviar" value="COMPROBAR" />
</form>

Y este es el código completo del archivo demo-captcha-form.php, con comentarios:

demo-captcha-form.php
<?php
  //iniciamos sesion
  session_start();	
  //declaramos una variable para el mensaje				
  $_SESSION['mensaText']= "";
  //si no existe el codigo captcha...
  if ( !isset($_POST['captchaCode']) ) {
    //escribimos el mensaje de inicio
    $_SESSION['mensaText']='Escribe el codigo Captcha';
  //si existe, pero no coincide con la variable de sesion captchaKey
  } elseif( md5($_POST['captchaCode'])!=$_SESSION['captchaKey'] ) {
    //escribimos el mensaje de error
    $_SESSION['mensaText']='El Captcha no es correcto';
  //si no se cunple nada de lo anterior (si el captcha es correcto)
  } else {
    //escribimos el mensaje de correcto
    $_SESSION['mensaText']='El Captcha es correcto';
    //vaciamos los campos del formulario
    unset($_POST); 
  }
?>
<form enctype="multipart/form-data" method="POST" action="">
  <?php echo $_SESSION['mensaText']; /* mensaje */ ?><br>
  <input name="captchaCode" placeholder="captcha" type="text" value="" />
  <img src="./demo-captcha.php" alt="captcha" title="Código Captcha" />
  <input type="submit" name="enviar" value="COMPROBAR" />
</form>
Escribe el codigo Captcha
* Esto es solo el aspecto visual del formulario final, no funciona la validación.

Demo

Y aquí está la demo totalmente funcional con validación incluida, aunque he tenido que hacer un par de cabriolas para incrustarla en el artículo…