Contraseñas de un solo uso

Este documento discute el empleo de contraseñas de un solo uso como método seguro de autentificación en aplicaciones sobre redes.

Escenario

Un problema clásico de las aplicaciones que trabajan en red es el de la autentificación.

Es un escenario frecuente tener que enviar los datos que nos identifican por un medio que no es seguro (Fig. 1).

Fig. 1
Fig. 1: Escenario

Nuestro cliente se ve expuesto a dos medios susceptibles de interceptar su nombre de usuario y contraseña: la LAN e Internet.

En estos casos hay diferentes soluciones que han demostrado ser eficaces (SSL, IpSec), pero que por un motivo u otro no siempre están disponibles para el desarrollador (no siempre es posible disponer de un servidor http con SSL).

En este documento presenta varias alternativas para llevar a cabo el proceso de autentificación de forma segura.

Encriptación de contraseñas

En primer lugar hay que tener en consideración la seguridad del servidor. Siempre se debe evitar el típico fichero con contraseñas en la forma: usuario contraseña. Las contraseñas siempre deben permanecer encriptadas en la máquina que realiza la autentificación.

Es recomendable el empleo de funciones hash (MD5, SHA, RIPEMD, ...) para almacenar las contraseñas. Estas funciones son irreversibles (teniendo el número generado por la función hash no existe el proceso inverso para obtener los datos que generaron ese número), por lo que es permisible que pudieran ser leidas por un intruso.

Almacenar la suma de comprobación de la contraseña empleando una función hash implica que si perdemos la contraseña, nos será imposible recuperarla ya que solo disponemos de del valor generado.

Parece que la solución está clara: como el valor resultante de la fórmula hash no es reversible, se puede enviar ese número para realizar la autentificación, evitando así el envío de contraseñas visibles.

Nada más lejos de la realidad.

El hecho de enviar las contraseñas encriptadas no soluciona en absoluto nuestro problema ya que, un intruso situado en el medio inseguro, puede interceptar la suma de comprobación para luego simplemente enviarla directamente al servidor.

Como no podemos evitar que los datos enviados se intercepten, nos encontramos ante una difícil situación.

Contraseñas de un solo uso (OTP)

Si los datos de identificación se pueden interceptar, podríamos disponer de contraseñas que solo fueran válidas para un uso, de forma que los datos interceptados estuvieran caducados cuando el intruso quisiera emplearlos.

Esta forma de actualización parece poco viable, ya que implica un cambio de contraseña por cada acceso, y además esa contraseña debería enviarse por un medio seguro.

No obstante, se puede implementar un sistema de contraseñas de un solo uso empleando el mismo medio inseguro para enviar enviar las 'nuevas contraseñas'. En realidad es el fundamento en que se basa, por ejemplo, SSL (aunque no soy un experto a ese respecto, que alguien me corrija si me equivoco).

Para emplear el sistema OTP (del ingés one time password) necesitamos definir:

  1. contraseña: es la contraseña que realmente conoce el cliente
  2. llave temporal: una contraseña temporal asociada a un instante en el tiempo y a los datos disponibles del cliente
  3. sesión: intervalo de tiempo en el cual tiene validez una llave temporal

Para la implantación del sistema necesitamos soporte para encriptación tanto en el cliente como en el servidor. Supongamos para la explicación el uso de MD5.

Fig. 2
Fig. 2: Algoritmo OTP

En la Fig. 2 se observa el algoritmo de contraseñas de un uso:

  1. El cliente pide una nueva sesión
  2. El servidor le devuelve un una llave temporal t que solo valdrá para esa sesión
  3. El cliente encripta la contraseña proporcionada por el usuario con MD5 obteniendo MD5_c. Es necesario ya que el servidor tiene almacenadas las contraseñas en esa forma. Entonces se procesa MD5_c empleando t. Ese procesamiento puede ir desde una concatenación hasta la aplicación del algoritmos de cifrado. De ese proceso se obtiene un MD5 que llamamos MD5_C, y es el valor que enviaremos al servidor.
  4. Ahora el servidor debe realizar el proceso que ya ha hecho el cliente para ver si obtiene el mismo MD5 recibido MD5_C. Para ello procesa MD5_s (la contraseña almacenada previamente encriptada con MD5) con t y aplica el MD5 para obtener MD5_S que finalmente es comparado con MD5_C.

Analicemos los púntos débiles del algoritmo: envío de datos.

Cuando el cliente pide una nueva sesión, el servidor genera t empleando la hora del sistema o un número aleatorio, asociando la IP del cliente a ese valor. El servidor no debe guardar t, sino el valor temporal o aleatorio. Posteriormente recalculará t en el paso 4 con la IP del cliente que envía los datos del paso 3. De esta forma evitamos que una máquina pida sesión y otra distinta conteste. Por supuesto esto no es infalible, aunque si proporciona un nivel aceptable de seguridad para muchos casos.

El cliente, en el paso 3, envía un MD5 por el medio inseguro. Ese dato podría ser interceptado, pero solo tiene validez para esa sesión, por lo que resultaría completamente inútil para un intruso.

Como se puede observar, realmente se emplea una contraseña cada vez, pero de forma automática y transparente para el usuario y el administrador.

Implementación: PHP con MD5

Supongamos una aplicación web sobre http que requiere de una autentificación segura via formulario y no se dispone de SSL.

La implementación requiere de:

El sistema requiere de dos partes diferenciadas: cliente y servidor.

La parte cliente consta de un formulario en el que se introducen los datos de autentificación. El documento lleva código JavaScript con una llave temporal, y se encarga de encriptar y procesar la contraseña mediante MD5 antes de enviar los datos mediante POST al servidor.

La parte servidor se encarga de, empleando variables de sesión, crear una sesión cuando se genera el formulario generando la llave temporal, y posteriormente verificar el MD5 enviado.

Implementación de ejemplo:


<?php
/*
*        Fichero: index.php
*    Descripción: Punto de entrada. login/password
*
*   Copyright (C) 2002 Juan J. Martinez <jjm at usebox.net>
*   This code must be distributed under GPL details.
*   GPL text is available here: http://www.fsf.org/licenses/gpl.txt
*/

/*
*
* Esta rutina se llama para comprobar si MD5_C es igual a MD5_S
*
*/
function autoriza() {
	
	session_start();
	$t=md5($_SESSION["t_parcial"] .getenv("REMOTE_ADDR"));
	
	/* ahora comprobariamos en nuestra base de datos el usuario y contraseña,
	   aquí simplificamos: user admin/ password admin (hardcoded) */
	
	if($_POST["i1"]=="admin" && md5("21232f297a57a5a743894a0e4a801fc3" . $t)==$_POST["i2"]) {
		
		/* estamos autorizados */
		unset($_SESSION["t_parcial"]);		
		echo "AUTORIZADO\n";
		
	} else {
		
		/* no estamos autorizados */
		session_unset();
		session_destroy();
		echo "Error de acceso\n";
		
	}
}

/*
*
* Genera el formulario con t para iniciar la sesión
*
*/
function muestraForm() {
	
	session_start();
	$_SESSION["t_parcial"]=md5(date("l dS of F Y h:i:s A"));		
?>

<html>
<head>
<title>Welcome</title>

<!-- proporciona hex_md5 -->
<script src="md5.js"></script>
<script language="JavaScript">
// ESTA PAGINA REQUIERE JAVASCRIPT ACTIVADO //<!--
function encrypt() {
	var md5_pre=hex_md5(document.login.i2.value)
	/* t = t_parcial + IP cliente */
	var md5=hex_md5(md5_pre+"<? echo md5($_SESSION["t_parcial"] .getenv("REMOTE_ADDR")); ?>")
	/* md5 es MD5_C */
	document.login.i2.value=md5
}
--></script>
</head>
<body>

<!-- genera el formulario -->
<br><br>
<div align="center">
<h2>Autentifiación</h2>
<table bgcolor="black" cellpadding="20" cellspacing="1" width="284" height="179">
<tr>
	<td bgcolor="#ffffff">
	<form name="login" action="index.php?accion=auth" method="POST" onsubmit="encrypt()">
		<div align="right">
		<p>User: <input type="text" size="15" name="i1"><br>
		Password: <input type="password" size="15" name="i2">
		<p><input type="submit" value="Login">
		</div>
	</form>
	</td>
</tr>
</table>
</div>
</body>
</html>
<?
}

/*
*
* Main()
*
*/
if($_GET["accion"]=="auth")
	autoriza();
else
	muestraForm();

?>

En el cliente se emplea una implementación de MD5 en JavaScript bajo licencia BSD disponible aquí.

Puedes bajar el código completo de la implementación (incluye md5.js) aquí.

Conclusiones

El empleo de contraseñas de un solo uso puede aumentar de forma considerable la seguridad de las autentificaciones en redes inseguras cuando no se dispone de recursos especiales para ese propósito (SSL, por ejemplo), de una forma sencilla y efectiva.

Posteriormente he descubierto que Yahoo! Mail parace utilizar este sistema (o silmilar) para autentificar a los usuarios cuando no se emplea SSL.

Si el lector encuentra un fallo o quiere comentar algún aspecto, puede ponerse en contacto mediante la dirección de correo que aparece al final del documento.

Última modificación: 23 Noviembre 2002.

Copyright © 2002 Juan J. Martínez <jjm at usebox.net>
Se permite la copia textual y distribución de este documento en su totalidad, por cualquier medio, siempre y cuando se mantenga esta nota de copyright.


FAQ

Cualquier comentario que envíes al autor sobre el artículo podrá aparecer aquí. Gracias a todos por la colaboración.

23 Nov 2002 - Posibles debilidades

Según comentas, el problema que intenta resolver ese método, es que alguien espíe el hash MD5 que circula por la red para hacerse pasar por ti. Sin embargo, alguien que espíe la comunicación en ambos sentidos, puede ver la clave t que el servidor envía al cliente, luego ver el MD5_C que envía el cliente, y hacer una sencilla resta MD5_c = MD5_C - t, para saber el hash de la clave del cliente. Y ahora puede conectarse al servidor cuando quiera, y hacerse pasar por el cliente, puesto que solo tiene que recibir el t, sumarlo al MD5_c recien calculado, y enviar esa suma.

Eso no es posible, mira la implementación. Quizás el texto no quede todo lo claro que yo pretendía. En la descripción del algoritmo dice que MD5_C es un MD5, no una simple suma como tu sugieres.

MD5_C = md5( MD5_c + t )

No puedes obtener MD5_c a partir de MD5_C aunque tengas t. Solo te queda la fuerza bruta (y si sabes que t se concatena con MD5_c, si la fórmula es desconocida para ti... ni eso). Te quedaría, como decía, la fuerza bruta contra MD5_c ;-)