La solución que ha dado al problema matemático no es correcta.
Juan Mellado, 17 Mayo, 2008 - 09:29

Siguiendo los pasos que detallé en mi anterior post, he conseguido programar una pequeña clase que aplica el algoritmo MD5 sobre un bloque de datos de entrada y genera su hash correspondiente. Ejecutando una pequeña batería de pruebas, comprobando el resultado obtenido con el generado por otras herramientas, he podido verificar que el código funciona correctamente en el entorno que lo estoy probando, y con el volumen de datos que espero, es decir, en MSVC 9 y con apenas unos pocos caracteres correspondientes a las claves de entrada a las cuentas. A pesar de ser funciones muy sencillas, fijándome detenidamente en la implementación, he anotado unas cuantas cosas que debería revisar si en un futuro quiero utilizarlas en un entorno de producción: portabilidad, seguridad, escalabilidad, y velocidad. Creo que la función puede ofrecer resultados distintos en función de si la máquina utiliza la convención big-endian o little-endian. Probablemente debería borrar el buffer intermedio que utilizo para proporcionar un extra de seguridad. La longitud calculada en bits puede exceder el tamaño de la variable que utilizo para almacenar su cálculo con bloques extremadamente grandes. Y es más, para un gran volumen de datos los requerimientos de memoria se disparan al reservarse tantos bytes extra como tenga el bloque pasado como argumento. Bufff... demasiadas cosas para algo tan aparentemente simple, pero la experiencia ha merecido la pena.

Retornando al tema de la seguridad, y pensando en como funciona el algoritmo MD5, es claro que no es la solución definitiva. Uno de los problemas más evidentes que tiene se basa en su filosofía de funcionamiento. Dada una misma cadena de texto siempre se retorna el mismo código hash. Tomando un diccionario de palabras es fácil generar los hashs para todas ellas y seguir el camino contrario. Es decir, capturar de alguna forma un código hash, y buscar la palabra a la que corresponde dicho código. De hecho, hay webs en Internet que ofrecen ese tipo de servicio. Como tradicionalmente las personas tendemos a utilizar palabras fáciles de recordar para las claves, entonces existe cierta probabilidad de encontrar alguna clave de alguien que no haya tenido especial cuidado a la hora de elegirla. Para resolver este problema se debe forzar a los usuarios a escoger claves que impliquen cierta dificultad. Las reglas más habituales son obligar a que tengan un mínimo de caracteres, que incluyan números o caracteres especiales, que no contengan el mismo carácter repetido más de ciertas veces, que no sean iguales a los nombres de los usuarios u otras palabras evidentes, y así sucesivamente.

Buscando un poco más acerca de este tema, encontré algunas técnicas para añadir un plus de seguridad a la hora de proteger las passwords de los usuarios en las comunicaciones con el servidor. La que me pareció más interesante fue la opción de que el servidor generara una clave secreta de forma aleatoria, que enviara esta clave al cliente, y que el cliente tuviera que devolvérsela al servidor codificada junto con la password del usuario. De esta forma se evita que un hash capturado pueda reutilizarse, ya que una clave secreta aleatoria sólo es válida para una conexión concreta, no para todas. HMAC (keyed-Hash Message Authentication Code) es un tipo de código para la autentificación de mensajes que puede adaptarse al proceso que acabo de describir, que se encuentra definido en el RFC 2104, y que puede utilizarse conjuntamente con cualquier función de encriptación basada en la iteración sobre bloques, como MD5, motivo por lo que me decidí a implementarlo también.

HMAC llama H a la función de encriptación (MD5 en mi caso), B a la longitud en bytes de los bloques que procesa H en cada iteración (64 bytes en el caso de MD5), L a la longitud en bytes del hash generado por H (16 bytes en el caso de MD5), y K a la clave secreta. Impone que la longitud inicial de K sea menor o igual que B, de forma que si la longitud de K es mayor que B entonces se puede aplicar H sobre K para obtener una nueva K de longitud L. Lo que no se recomienda en ningún caso es que K tenga una longitud menor de L, ya que esto restaría efectividad al método. Una vez obtenida una K del tamaño adecuado, esta ha de complementarse con ceros por la derecha hasta que alcance la longitud de B bytes.

A continuación se definen dos constantes. La primera llamada ipad, una cadena de B bytes que contienen todos ellos el valor 0x36. La segunda llamada opad, una cadena de B bytes que contienen todos ellos el valor 0x5C.

El proceso de codificación en si mismo consiste en ejecutar la siguiente función sobre el bloque de datos que se quiere proteger al que se denota como text:


H( CONCAT(K XOR opad, H( CONCAT(K XOR ipad, text) ) ) )

Es decir, primero se hace XOR entre ipad y K. A continuación se concatena con text. Se aplica la función H de encriptación. Al resultado de H se le concatena el XOR entre K y opad. Y se vuelve a aplicar la función H otra vez para obtener el resultado final.

Es claro que el resultado de esta función será un bloque de L bytes, es decir, la longitud de los hashs que genera H, ya que es el último paso que se aplica. De igual forma que deber ser claro que los valores "K XOR opad" y "K XOR ipad" pueden calcularse una única vez al principio cuando el servidor mande la clave secreta, y reutilizarlos para comprobar la autenticidad de una serie de mensajes, y no sólo el de validación de password. Aunque, para mayor seguridad, se recomienda que el servidor cambie la clave secreta de un cliente varias veces a lo largo del tiempo dentro de una misma conversación.

Siguiendo las indicaciones dadas es fácil desarrollar una función que evalúe HMAC sobre MD5. Aunque siempre queda la posibilidad de tomar código existente. Hay una implementación disponible en prácticamente cualquier lenguaje de programación, tanto de MD5 como de HMAC. Incluso los gestores de base de datos como MySQL y PostgreSQL soportan los métodos de encriptación más populares de forma nativa a modo de funciones SQL (1 y 2). Además, en bastantes proyectos de código abierto pueden encontrarse implementaciones sólidas y altamente probadas, como dentro de los fuentes de OpenSSL por ejemplo.

Para terminar, comentar que MD5 no es el método más recomendado hoy en día, todo lo contrario, en prácticamente todos los sitios que he acabado visitado se recomendaba el uso de otros métodos como SHA-1 (RFC 3174) o RIPEMD-160.

Responder

Por favor introduzca el resultado del problema propuesto.
El contenido de este campo se mantiene como privado y no se muestra públicamente.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Saltos automáticos de líneas y de párrafos.

Más información sobre opciones de formato