Para este artículo me estoy basando en el algoritmo descrito por
Juan J. Martínez, el cuál describe como emplear contraseñas de un solo uso. En el artículo original ya existe una implementación del algoritmo, pero nuevamente al igual que en mi anterior artículo, el problema es que es para PHP. Bien, después de esta pequeña introducción, veamos lo que dice el algoritmo.
1. El cliente pide una nueva sesión
2. El servidor devuelve un una llave temporal "t" que sólo valdrá para esa sesión
3. El cliente encripta la contraseña proporcionada por el usuario, con MD5, obteniendo md5_c y a su vez se procesa con t. El procesamiento de md5_c con t, puede ir desde una concatenación hasta la aplicación del algoritmos de cifrado, de este proceso se obtiene un MD5 que llamaremos 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.
El algoritmo puede sonar un poco complicado, pero no lo es tanto, bien ahora pasemos a la implementación que seguramente aquí se aclararán muchas dudas. Para la implementación voy a empezar por definir los supuestos:
A. Supongamos que tenemos una BD en SQL Server llamada BDCLAVES, la cual contiene una tabla llamada Usuarios con la siguiente estructura:
Tabla Usuarios
Campo Tipo
PK Id INT (IDENTITY)
Usuario NVARCHAR(100)
Clave NVARCHAR(100)
B. Supongamos que dentro de la tabla Usuarios tenemos dos registros:
Tabla Usuarios
Id Usuario Clave
1 admin 21232f297a57a5a743894a0e4a801fc3
2 usuario f8032d5cae3de20fcec887f395ec9a6a
***Como se puede observar el contenido del campo clave se encuentra encriptado usando MD5
Paso 1
"El cliente pide una nueva sesión"
Para este paso no tenemos ninguna implementación, ya que esto se traduce a que el cliente abra su navegador, teclee la dirección de nuestro sitio y la página sea mostrada.
Paso 2
"El servidor devuelve una llave temporal 't' que sólo valdrá para esa sesión"
Para hacer la implementación de esto haremos uso del siguiente código:
<%
if request.Form="" then
session("t_temporal") = md5(now())
end if
%>
Lo que este código quiere decir es que, si es la primera vez que se esta solicitando la página, entonces en el objeto sesión genere una variable llamada
"t_temporal" y le asigne el MD5 de la cadena que se genere usando la fecha y hora de ese momento. Con ésto estamos logrando el objetivo estamos generando una llave que sólo valdrá para esta sesión, así si llega otra petición el MD5 generado será distinto, debido a que la función NOW() regresa una cadena que incluye fecha y hora(hh:mm:ss a.m./p.m.).
Para realizar correctamente la implementación de este paso, necesitamos el script para generar md5 con ASP, la liga para bajar este script es la siguiente:
http://userpages.umbc.edu/~mabzug1/cs/md5/md5.asp
Paso 3
"El cliente encripta la contraseña proporcionada por el usuario, con MD5, obteniendo md5_c y a su vez se procesa con t. El procesamiento de md5_c con t, puede ir desde una concatenación hasta la aplicación del algoritmos de cifrado, de este proceso se obtiene un MD5 que llamaremos MD5_C, y es el valor que enviaremos al servidor"
Muy bien, empezemos por definir que es t, en nuestro caso t es un MD5 que resulta de la concatenación de dos cadenas, t_temporal y el Id de sesión del usuario. En el artículo original ocupa la IP del cliente, pero supongamos que un grupo de nuestros usuarios ocupan un proxy, entonces la IP para todos ellos es la misma y esto generaría que una parte de t sea constante para estos casos; en cambio si ocupamos el Id de sesión este siempre es distinto sin importar que un grupo de clientes ocupen un proxy, y así aseguramos que t no tenga partes constantes.
Como el algoritmo lo dice, esta parte se va a implementar del lado del cliente, por lo que necesitamos un script para obtener MD5, aqui la liga para descargar el script:
http://pajhome.org.uk/crypt/md5/
Ya que hemos definido lo anterior, la implementación del lado del cliente es así:
<html>
<head>
<title>Contraseñas de un uso</title>
<script type="text/javascript" src="md5.js"></script>
<script>
function ObtenMD5_C(forma)
{
//Pasamos el nombre de usuario a un campo oculto
forma.usuario.value = forma.usuario1.value
//Generamos t del lado del cliente
var t = "<%=md5(session("t_temporal")+session.SessionID)%>"
//En esta parte obtenemos md5_c
var md5_c = hex_md5(forma.clave1.value)
//Ahora obtenemos MD5_C
var MD5_C = hex_md5(md5_c+t)
//Ya que tenemos MD5_C, lo guardamos en un campo oculto
forma.clave.value = MD5_C
//El siguiente código es para limpiar visualmente los campos
forma.usuario1.value = ""
forma.clave1.value = ""
//Se envía el formulario
forma.submit()
}
</script>
</head>
<body>
<!--Formulario para solicitar usuario y contraseña-->
<form name="validacion" action="un_uso.asp" method="post">
<input type="hidden" name="usuario" />
<input type="hidden" name="clave" />
Usuario <input type="text" name="usuario1" size="20" /><br />
Contraseña <input type="password" name="clave1" size="20" /><br />
<input type="button" value="Ingresar" onclick="ObtenMD5_C(this.form)" />
</form>
</body>
</html>
Muy bien, ya tenemos la parte del cliente. En la función
ObtenMD5_C, tanto el valor para usuario como para MD5_C, lo estamos almacenando en campos ocultos y los campos visuales (usuario1 y clave1) los estamos limpiando por las siguientes razones:
1. Para que el proceso sea totalmente transparente hacia el usuario y no vea ninguna variación en la clave que proporcionó; esto quiere decir que si el valor de MD5_C lo volvemos a colocar en el campo clave1, el usuario vería que la clave que proporcionó, de repente tiene más caracteres que los que ingresó, entonces para no dar esa idea de "manipulación", todo lo mandamos a campos ocultos.
2. Si mantenemos el texto en el campo usuario1 al momento de enviar la información, hay veces que el campo guarda un historial de los textos que se han introducido, entonces para evitar esto quitamos la información.
Paso 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 encriptada con MD5) con t y aplica el MD5 para obtener MD5_S que finalmente es comparado con MD5_C"
Para realizar la implementación de este paso, usamos el siguiente código:
Bien ya hemos implementado todo el algoritmo, ahora veamos todo el código:
<%
'Recibimos las variables
txtusuario = request.Form("usuario")
MD5_C = request.Form("clave") 'Clave es el valor de MD5_C
'Se realiza la conexión a la Base de Datos
basedatos="Driver={SQL Server};"& _
"Server=LOCALHOST;"& _
"Database=BDCLAVES;"& _
"Uid=sa;"& _
"Pwd=;"
'Se crea el objeto de tipo conexión para la BD
set conexion = server.CreateObject("ADODB.Connection")
'Se abre la BD
conexion.open(basedatos)
'Declaramos la consulta, para obtener el password del usuario
consulta = "SELECT "& _
" Clave "& _
"FROM "& _
" Usuarios "& _
"WHERE "& _
" Usuario = '"&txtusuario&"' "
'Se ejecuta la consulta y el resultado se guarda en un objeto
set resultado = conexion.execute(consulta)
'Primero validamos que el usuario haya existido, o dicho de otra forma
'que la consulta nos haya regresado un resultado
if not resultado.eof then
'Obtenemos t
t = md5(session("t_temporal")&session.SessionID)
'Obtenemos md5_s
md5__s = resultado("clave")
'Obtenemos MD5_S
MD5_S = md5(md5__s&t)
'Se valida que las dos cotraseñas sean iguales
if MD5_S = MD5_C then
'Si las claves fueron iguales entonces, para este ejemplo, mostramos un mensaje
'de éxito en la validación
txtmsj = "alert('¡Usted ha sido validado en la base de datos!')"
else
'Si las claves no fueron iguales, se crea el mensaje de error en javascript
'para después mostrarlo
txtmsj = "alert('La contraseña es incorrecta, revise mayúsculas y minúsculas')"
end if
else
'Si la consulta no dio ningún resultado, quiere decir que el usuario que se
'introdujo no existe, entonces se crea un mensaje de error para mostrarlo al usuario
txtmsj = "alert('El usuario "&txtusuario&" no existe en la base de datos')"
end if
set resultado = nothing
'Se cierra la conexión y se libera la memoria
conexion.close()
set conexion = nothing
%>
Bien ya hemos implementado todo el algoritmo, ahora veamos todo el código unido.
(un_uso.asp)
<%@LANGUAGE="VBSCRIPT" CODEPAGE="1252"%>
<!--#INCLUDE file="md5.asp"-->
<%
'Declaramos las variables a ocupar
Dim txtusuario, MD5_C, MD5_S, md5__s, txtmsj, basedatos, t
Dim conexion, consulta, resultado
'Se valida que sea la primera vez que se muestra el formulario para general t_temporal
if request.Form="" then
session("t_temporal") = md5(now())
else
'Se reciben las variables
txtusuario = request.Form("usuario")
MD5_C = request.Form("clave")
'Se realiza la conexión a la Base de Datos
basedatos="Driver={SQL Server};"& _
"Server=LOCALHOST;"& _
"Database=BDCLAVES;"& _
"Uid=sa;"& _
"Pwd=;"
'Se crea el objeto de tipo conexión para la BD
set conexion = server.CreateObject("ADODB.Connection")
'Se abre la BD
conexion.open(basedatos)
'Declaramos la consulta, para obtener el password del usuario
consulta = "SELECT "& _
" Clave "& _
"FROM "& _
" Usuarios "& _
"WHERE "& _
" Usuario = '"&txtusuario&"' "
'Se ejecuta la consulta y el resultado se guarda en un objeto
set resultado = conexion.execute(consulta)
'Primero validamos que el usuario haya existido, o dicho de otra forma
'que la consulta nos haya regresado un resultado
if not resultado.eof then
'Obtenemos t
t = md5(session("t_temporal")&session.SessionID)
'Obtenemos md5_s
md5__s = resultado("clave")
'Obtenemos MD5_S
MD5_S = md5(md5__s&t)
'Se valida que las dos cotraseñas sean iguales
if MD5_S = MD5_C then
'Si las claves fueron iguales entonces, para este ejemplo, mostramos un mensaje
'de éxito en la validación
txtmsj = "alert('¡Usted ha sido validado en la base de datos!')"
else
'Si las claves no fueron iguales, se crea el mensaje de error en javascript
'para después mostrarlo
txtmsj = "alert('La contraseña es incorrecta, revise mayúsculas y minúsculas')"
end if
else
'Si la consulta no dio ningún resultado, quiere decir que el usuario que se
'introdujo no existe, entonces se crea el mensaje de error en javascript
'para después mostrarlo al usuario
txtmsj = "alert('El usuario "&txtusuario&" no existe en la base de datos')"
end if
set resultado = nothing
'Se cierra la conexión y se libera la memoria
conexion.close()
set conexion = nothing
end if
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Contraseñas de un uso</title>
<script type="text/javascript" src="md5.js"></script>
<script>
function ObtenMD5_C(forma)
{
//Pasamos el nombre de usuario a un campo oculto
forma.usuario.value = forma.usuario1.value
//Generamos t del lado del cliente
var t = "<%=md5(session("t_temporal")+session.SessionID)%>"
//En esta parte obtenemos md5_c
var md5_c = hex_md5(forma.clave1.value)
//Ahora obtenemos MD5_C
var MD5_C = hex_md5(md5_c+t)
//Ya que tenemos MD5_C, lo guardamos en un campo oculto
forma.clave.value = MD5_C
//El siguiente código es para limpiar visualmente los campos
forma.usuario1.value = ""
forma.clave1.value = ""
//Se envía el formulario
forma.submit()
}
<%
'Si existió algún error se muestra aquí
response.Write(txtmsj)
%>
</script>
</head>
<body>
<!--Formulario para solicitar usuario y contraseña-->
<form name="validacion" action="un_uso.asp" method="post">
<input type="hidden" name="usuario" />
<input type="hidden" name="clave" />
Usuario <input type="text" name="usuario1" size="20" /><br />
Contraseña <input type="password" name="clave1" size="20" /><br />
<input type="button" value="Ingresar" onclick="ObtenMD5_C(this.form)" />
</form>
</body>
</html>
Como podemos ver la implementación del algoritmo esta directamente en la página ASP, decidí hacerlo de esta forma para no depender del motor de base de datos, aunque podría quedar mejor si el proceso para la obtención de MD5_S se hiciera directamente en el motor de base de datos. Así como esta ahora bien podemos ocupar SQL Server, Access o MySQL como motor, sólo tendríamos que cambiar la línea del Driver para la conexión y en su caso, la sintáxis de la consulta, sólo en caso de ser necesario.
En la sección de códigos ASP está disponible todo el código fuente y demás scripts necesarios para este artículo.