Desde edades muy tempranas de Internet se ha utilizado el HTTP no sólo para compartir información con los demás. También se ha utilizado profusamente para pedir datos al usuario, bien para almacenarlos, bien para realizar alguna acción con ellos y devolver el resultado al usuario.
Imaginemos los siguientes escenarios:
En todos los casos, queremos ofrecer un servicio a los usuarios (por diferentes motivos: de forma altruista, por hacernos populares, por dar a conocer nuestra empresa o productos, etc...). Sin embargo, es posible que otros sistemas nos perjudiquen o se aprovechen de nuestro trabajo (como ocurre con frecuencia). Así, algunas amenazas para los ejemplos anteriores serían:
Como se ve, existen potenciales riesgos al dejar al público una serie de servicios.
Uno de los mecanismos más populares para que un servidor HTTP pueda identificar si la petición a un determinado servicio ha sido realizada por un humano o un autómata, son los CAPTCHA que consisten en una imágen con algunos caracteres que debe reescribir la persona que quiere acceder. Un ejemplo típico, sería el siguiente:
No es fácil responder la pregunta sin entrar en aspectos técnicos, puesto que la dificultad que tiene un ordenador en resolver este tipo de problemas proviene de cómo éste procesa la información. A grandes rasgos, podríamos resumirlo en lo siguiente:
Romper un captcha significa encontrar un procedimiento (algoritmo) que permita a un ordenador automatizar el reconocimiento de la secuencia que contiene la imagen (y que es la que debe suministrarse para acceder al servicio). Parece ser que la mejor forma de romper los captcha es utilizar redes neuronales, sin embargo, para generadores de captcha suficientemente buenos, la cosa se complica lo suficiente como para que el esfuerzo a realizar (para entrenar la red) sea muy grande. Una de las cosas más difíciles es saber separar los diferentes símbolos dentro de una imagen (los cuales, se suelen superponer parcialmente).
Desde mi punto de vista, la lucha entre el generador de captchas y el hacker sería como la fórmula 1, en la que no obstante, el generador tiene todas las de ganar, puesto que la explosión combinacional que se logra añadiendo pequeñas variaciones complican enormemente el trabajo al hacker (por ejemplo, teniendo que reprogramar toda una nueva red neuronal).
Los más populares son aquellos con unos 5 números o letras, otros contienen palabras e incluso algunos, unas simples operaciones matemáticas (por ejemplo 5 + 2 * 4). No he visto ninguno, pero otros captchas muy fuertes podrían hacer preguntas muy sencillas que variaran de forma automática la forma de la frase ("¿de qué color es el cielo?", "¿cuantas patas tiene un gato?") y aunque tendría algunas desventajas, sería realmente fuerte.
Es seguro que podrían (como en el ejemplo anterior) buscarse variantes o estrategias más o menos parecidas de captcha, sin embargo, el captcha "estandar" es realmente sencillo de implementar.
Muchas variantes complican mucho más el reconocimiento automatizado de captchas como: situar las letras aleatoriamente en la imagen (y no alineadas), superponer parcialmente unas letras con otras, dibujar diferentes letras de diferentes tamaños, aplicar filtros no lineales que deformen la imagen o cada letra, etc...
Una forma muy sencilla (y creo que elegante) de hacer un captcha en PHP es la siguiente:
La única desventaja es que en la misma sesión sólo puede haber un captcha simultáneamente, pero ésto debería ser lo normal. La ventaja es que es completamente seguro (a no ser que rompan nuestro generador de captchas, claro) y muy sencillo, pues sólo requiere una variable de sesión.
Si no disponemos de sesión o el objeto de sesión no es compartida entre peticiones de página (algo raro pero que podría darse si el servidor que ofrece el servicio es diferente al servidor que lo da) una solución es aportar junto con la imagen que genera el captcha el código ¡pero encriptado claro!. Aunque ésto es algo más feo y engorroso.
Bueno, yo creo que con el código directamente del script PHP que genera mi captcha es suficiente, lo he comentado bastante así que, no deberías tener problemas.
<?php /* JJBM, 26/03/2009, captcha.php Genera una imágen CAPTCHA y almacena en la sesión el código establecido. (Obviamente sólo puede usarse una imagen CAPTCHA por sesión a la vez). */ // Configuración: $N = 2; // Nivel de emborronado { 2, 3, 4, ... } $J = 100; // Calidad JPEG { 0, 1, 2, 3, ..., 100 } $M = 5; // Margen. $L = 2; // Número de letras. $C = FALSE; // Case sensitive. // Acceso a los objetos de sesión: session_start(); // Indicamos que vamos a generar una imagen ¡no una página HTML! header("Content-type: image/jpeg"); // Inicializamos cualquier posible valor previo de captcha: $_SESSION['CAPTCHA'] = ''; // Metemos tantos caraceteres aleatorios como sean precisos: for( $n = 0; $n < $L; $n++ ) $_SESSION['CAPTCHA'] .= C(); // Si no es case sensitive lo ponemos todo en minúsculas: if( ! $C ) $_SESSION['CAPTCHA'] = strtolower( $_SESSION['CAPTCHA'] ); // Dimensiones del captcha: $w = 2 * $M + $L * imagefontwidth ( 5 ); $h = 2 * $M + imagefontheight( 5 ); // Creamos una imagen: $i = imagecreatetruecolor( $w, $h ); // La rellenamos de blanco: imagefill( $i, 0, 0, imagecolorallocate( $i, 255, 255, 255 ) ); // Elegimos aleatoriamente un ángulo de emborronado: $A = ( rand() % 180 ) / 3.14; // Realizamos iteraciones de emborronado: for( $n = 0; $n < $N; $n++ ) { // Factor de interpolación, va de 1.0 a 0.0 $t = 1.0 - $n / ( $N - 1.0 ); // El radio se va centrando a medida que se hace nítido: $r = $M * $t; // El color va siendo cada vez más oscuro: $c = 255 * $t; $c = imagecolorallocate( $i, $c, $c, $c ); // Trazamos dos líneas aleatorias para dificultar más las cosas: imageline( $i, $M, rand( $M, $h - $M ), $w - $M, rand( $M, $h - $M ), $c ); imageline( $i, rand( $M, $w - $M ), $M, rand( $M, $w - $M ), $h - $M, $c ); // Pasamos un filtro gaussiano: imagefilter( $i, IMG_FILTER_GAUSSIAN_BLUR ); // Dibujamos el texto en el sentido del ángulo y radio de desplazamiento: imagestring( $i, 5, $M + $r * cos( $A ), $M + $r * sin( $A ), $_SESSION['CAPTCHA'], $c ); // Pasamos otro filtro gaussiano: imagefilter( $i, IMG_FILTER_GAUSSIAN_BLUR ); } // Escribimos la imagen como un JPEG en el buffer de salida: imagejpeg( $i, NULL, $J ); // Liberamos la imagen: imagedestroy( $i ); // Devuelve un caracter aleatorio: function C() { $W = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; return substr( $W, rand() % strlen( $W ), 1 ); } ?>