Éste es un algoritmo de los que se conocen como de sólo ida, ya que no es posible desencriptar lo que se ha encriptado. Puede ser que a primera vista no se le vea la utilidad, pero en los siguientes dos escenarios, éste es el algoritmo necesario para realizar el proceso. En éstos, el primero es proteger información muy valiosa encriptando el contenido y el segundo es validar que no se modifique la información que se está enviando desde algún lugar a otro. Veamos el primer escenario.
Almacenar contraseñas de un sistema. Las contraseñas de cualquier sistema serio jamás debieran poder desencriptarse. El motivo es simple. Si se pueden desencriptar, el riesgo que alguien conozca la llave y tenga acceso a todo el sistema con todos los usuarios es muy grande. Entonces, si no puedo desencriptar una contraseña, ¿Cómo puedo saber entonces si una contraseña entregada es válida si no puedo compararla? La respuesta es muy simple. Se encripta la contraseña entregada y se compara el resultado de la encriptación con la contraseña previamente almacenada.
Problemas: Si bien el problema no está tan relacionado con el Hash, debido a la utilización de contraseñas de escasa dificultad, haciendo un ataque de fuerza bruta o por diccionario se podrían encontrar valores de Hash que coinciden con el Hash de las contraseñas almacenadas. Para esto se pueden ejecutar dos planes de acción independientes, pero obteniéndose el mejor resultado con la utilización de ambos.
La solución 1 consiste en realizar varias pasadas de Hash sobre los datos, aplicándole el Hash al resultado del Hash anterior. Realizando esto varios cientos de veces (idealmente mil veces) podemos dificultar más el trabajo de un hacker.
La solución 2 requiere agregar un texto adicional a la contraseña. Esto se conoce como Salt. Entonces, cada vez que un usuario cambie o esté validando una contraseña, hay que concatenarle a la contraseña este Salt y de ahí generar el Hash. De esta forma le agrega variación a las contraseñas pobremente definidas. Esta solución requiere un trabajo adicional ya que es necesario además almacenar el texto del Salt junto a la información del usuario y la contraseña encriptada. Este texto se debe crear con caracteres generados al azar.
En el ejemplo se mostrará la combinación de ambas soluciones. Como encriptación de Hash se usará SHA1. Los otros algoritmos mencionados con anterioridad están disponibles también en .NET.
{
string _strPasswordSalt = strPassword + strSalt;
SHA1 _objSha1 = SHA1.Create();
byte[] _objTemporal = null;
try
{
_objTemporal = System.Text.Encoding.UTF8.GetBytes(_strPasswordSalt);
for (int i = 0; i <= intIteraciones-1; i++)
_objTemporal = _objSha1.ComputeHash(_objTemporal);
}
finally
{
_objSha1.Clear();
}
return _objTemporal;
}
El resultado de este Hash (Sha1) siempre retorna 160 bits, es decir, 20 bytes. Cuidado con confundir con el largo del string resultante de la conversión a Base64. Esta conversión genera más caracteres. Para verificar, pueden medir el largo del arreglo colocando un punto de interrupción en el retorno (return), y notarán que son 20 bytes.
Además, las funciones de hash no tienen limitante para el tamaño del texto de entrada. Sea cual sea el tamaño, el largo de la salida es el mismo y se procesan todos los caracteres del string de entrada.
Para un sistema de almacenamiento de contraseñas de usuario seguro, hay que hacer algunas modificaciones a esta función, pero son mínimas. Las modificaciones no pasan por cambios en el algoritmo, sino mas bien por crear una función para que genere Salt randómicos y crear la función para la comparación de las contraseñas.
La función para comparar arreglos de Bytes es la siguiente
{
if (arrayA.Length != arrayB.Length) return false;
for (int i = 0 ; i <= arrayA.Length – 1; i++)
if (!arrayA[i].Equals(arrayB[i])) return false;
return true;
}
Patrick Mac Kay
Noviembre 2004.