El blog del burgués

19 marzo 2010

Códigos Hash en .NET Framework

Filed under: C# — elburgues @ 7:31 AM

Un código hash es un valor numérico de longitud fija que representa o identifica unívocamente una cantidad de datos mayor, siendo el código hash un valor numérico mucho menor (bueno, digo unívocamente pero, para ser exactos, es mejor decir que técnicamente es poco factible encontrar dos mensajes distintos cuyo valor hash sea el mismo). O sea que, un algoritmo hash es un procedimiento matemático bien determinado que representa una cantidad (potencialmente) grande de datos mediante un entero mucho menor. Si aplica un algoritmo hash a un párrafo de texto simple y cambia solo una letra del párrafo, el valor hash subsiguiente producirá un valor distinto. Si el valor hash es criptográficamente seguro, su valor cambiará significativamente. Por ejemplo, si se cambia únicamente un solo bit de un mensaje, una función hash segura puede generar un resultado que difiera en un 50%.

Uso de los algoritmos hash

Los algoritmos hash se utilizan en:

• Las firmas digitales, ya que puede firmar con más eficacia un valor hash que un valor mayor. Las firmas digitales están a menudo compuestas de valores hash cifrados. Por tanto, cifrar los valores hash es un uso eficaz de recursos computacionales y de ancho banda. Dependiendo de sus requisitos de seguridad, puede elegir cifrar el valor hash de un mensaje en lugar de cifrar el propio mensaje.

• Comprobar la integridad de datos enviados a través de canales inseguros. El valor hash de los datos recibidos puede compararse con el de los datos enviados para determinar si se alteraron.

Las funciones hash se denominan también funciones unidireccionales porque es sencillo aplicar una función hash a un mensaje, pero es matemáticamente inviable obtener el mensaje a partir de un código hash.

Tipos de algortimos hash

.NET Framework incluye clases para 5 algoritmos hash diferentes, aunque, cuatro de ellos están estrechamente relacionados. La siguiente tabla muestra la lista (la explicamos a continuación):

Nombre

Límite del mensaje (bits)

Tamaño del código Hash (bits)

MD5

264

128

SHA-1

264

160

SHA-256

264

256

SHA-384

2128

384

SHA-512

2128

512

  • Tamaño del código Hash (bits): La longitud de un código hash se mide en bits, lo que significa que un código hash de 64 bits puede representar 2^64 valores hash diferentes (18.446.744.073.709.551.616). Por ejemplo, a un ritmo de comprobación de 10 millones de mensajes por segundo, se tardaría unos 58.494 años para encontrar una coincidencia. Es computacionalmente factible, pero muy poco probable. Independientemente del tamaño o el contenido del mensaje procesado, los algoritmos hash producen siempre códigos hash de una longitud determinada, lo que significa que hay un conjunto finito de valores hash (se puede crear un número infinito de mensajes diferentes, pero sólo hay un número finito de códigos hash posibles, por lo tanto, es posible encontrar un par de mensajes que producen el mismo código hash).
  • MD5: Es el algoritmo hash más rápido incluido en .NET Framework, pero el tamaño del código hash lo hace más susceptible a los ataques por  fuerza bruta y los ataques de cumpleaños. Se detectaron defectos de diseño en MD5 y ya no se consideró seguro.
  • SHA-1: Un número de ataques significativos fueron divulgados sobre SHA-1, lo que ha planteado dudas y también se recomienda abandonarlo. Si quieres saber más, entra aquí.
  • SHA-256, SHA-384, y SHA-512: Son variaciones de SHA-1. Aunque  SHA-256, SHA-384, y SHA-512 producen códigos hash más largos, un código hash más largo no proporciona una mayor seguridad si el algoritmo subyacente tuviera algún fallo. Dando más bits solo se incrementa el esfuerzo para encontrar colisiones pero según avancen los métodos de criptoanálisis y la potencia de los ordenadores se irán recortando los tiempos y estaremos en las mismas.

Integridad de los mensajes

Dos partes remotas podrían utilizar una función hash para intentar asegurar la integridad de los mensajes, seleccionando previamente un algoritmo hash a utilizar por ambos:

  • 1er Método:
    • Ana escribe un mensaje y, a continuación, crearía un valor hash de ese mensaje utilizando el algoritmo escogido.
    • Ana envía a Jose mediante un canal no seguro el mensaje y el valor hash creado.
    • Jose recibe el mensaje y le aplica el algoritmo hash con el algoritmo seleccionado.
    • Jose compara su valor hash con el valor hash que recibió de Ana.
  • 2º Método:
    • Ana escribe un mensaje y, a continuación, crearía un valor hash de ese mensaje utilizando el algoritmo seleccionado.
    • Ana envía a Jose mediante un canal privado seguro el valor hash que acaba de calcular. Ana está ocultando ese valor a todo el mundo salvo a Jose.
    • Ana envía a Jose mediante un canal no seguro el mensaje.
    • Jose recibe el mensaje y le aplica el algoritmo hash con el algoritmo seleccionado.
    • Jose compara su valor hash con el valor hash que recibió de Ana.

Con ambos métodos, si los dos valores hash son idénticos, el mensaje no se ha modificado. Si los valores no son idénticos, el mensaje se modificó después de que Ana lo enviara. Este método no autentica, ya que cualquiera puede suplantar a Ana y enviar un mensaje a Jose. Todo lo que Jose podría determinar es que el mensaje coincide con su firma (ésta es una forma de ataque de tipo “Man in the middle” de la que espero hablar en otra entrada). Estos métodos tampoco impiden que alguien lea los mensajes de Ana, ya que se transmiten sin encriptar. Para conseguir una seguridad total, normalmente se necesitarán firmas digitales (de las que también espero hablar otro día) y mecanismos de cifrado. Por tanto, se puede decir que los algoritmos hash proporcionan integridad a un mensaje, pero no autenticación ni privacidad.

Ejemplos en C#.NET

Hay dos tipos de implementaciones incluidas en .NET Framework, aquellas cuyos nombres terminan en Managed (por ejemplo SHA1Managed, cuyas implementaciones están escritas en un lenguaje manejado de .NET, por ejemplo C# o VB .NET) y aquellas cuyos nombres terminan en CryptoServiceProvider (por ejemplo, SHA1CryptoServiceProvider), las cuales se apoyan en la Crypto API de Windows (son más rápidas y consumen menos memoria). La Crypto API de Windows es un componente del sistema operativo de Windows que contiene funcionalidades criptográficas. Varias de las clases de .NET Framework se apoyan en características de la Crypto API para tareas como la gestión de claves o la implementación de algoritmos. Como programador de .NET, no necesitas entender los detalles de la Crypto API.

Generando un código hash a partir de datos alojados en memoria:

using System;
using System.Security.Cryptography;
using System.Text;

namespace Encription
{
    class Hash
    {
        [STAThread]
        public static void Main()
        {
            // Definimos el mensaje para el cual vamos a crear el código hash.
            String mensaje = "Programando seguridad en C#.NET";
            Console.WriteLine("Este es el mensaje a procesar: {0}", mensaje);
            Console.WriteLine("Pulse una tecla para continuar\n");
            Console.ReadKey();

            // Obtenemos un array de bytes a partir de dicho mensaje
            byte[] mensajeBytesArray = Encoding.Default.GetBytes(mensaje);

            // Instanciamos el algoritmo
            SHA512Managed algoritmoHash = new SHA512Managed();

            // Se podría haber instanciado de esta otra forma.
            // Si no proporcionas un valor al método Create
            // "SHA1CryptoServiceProvider" será usado como opción por defecto.
            // HashAlgorithm algoritmoHash = HashAlgorithm.Create("SHA512");

            // Obtenemos el código hash mediante el método ComputeHash
            // ComputeHash es un método sobrecargado.
            // Existe una versión con la cual poder procesar solamente una
            // subregión del array de bytes.
            byte[] codigoHashBytesArray = algoritmoHash.ComputeHash(mensajeBytesArray);

            // Imprimimos el código hash en hexadecimal en la consola
            Console.WriteLine("Este es el código hash obtenido para el mensaje:");
            Console.WriteLine(BitConverter.ToString(codigoHashBytesArray, 0));
            Console.WriteLine("Pulse una tecla para finalizar");
            Console.ReadKey();

            // Liberamos recursos.
            algoritmoHash.Clear();
        }
    }
}

Creando un código hash a partir de un stream de datos

Esta opción es útil para cuando se procesan archivos de datos o cuando se leen datos de una conexión de red. Con unas ligeras modificaciones sobre el código anterior, quedaría así:

using System;
using System.IO;
using System.Security.Cryptography;

namespace Encription
{
    class Hash
    {
        [STAThread]
        public static void Main()
        {
            // Creamos el stream de fichero.
            // C:\MisDatos.txt contiene la cadena: Programando seguridad en C#.NET
            Stream x_stream = new FileStream(@"C:\MisDatos.txt", FileMode.Open);

            // Instanciamos el algoritmo
            SHA512Managed algoritmoHash = new SHA512Managed();

            // Se podría haber instanciado de esta otra forma.
            // Si no proporcionas un valor al método Create
            // "SHA1CryptoServiceProvider" será usado como opción por defecto.
            // HashAlgorithm algoritmoHash = HashAlgorithm.Create("SHA512");

            // Obtenemos el código hash mediante el método ComputeHash
            // ComputeHash es un método sobrecargado.
            // Existe una versión con la cual poder procesar solamente una
            // subregión del array de bytes.
            byte[] codigoHashBytesArray = algoritmoHash.ComputeHash(x_stream);

            // Imprimimos el código hash en hexadecimal en la consola
            Console.WriteLine("Este es el código hash obtenido para el mensaje:");
            Console.WriteLine(BitConverter.ToString(codigoHashBytesArray, 0));
            Console.WriteLine("Pulse una tecla para finalizar");
            Console.ReadKey();

            // Liberamos recursos.
            algoritmoHash.Clear();
        }
    }
}

Comprobando la integridad

Bien, ya hemos visto la parte de Ana. Ahora vamos a ver cómo tendría que hacer Jose:

using System;
using System.Security.Cryptography;
using System.Text;

namespace Encription
{
    class Hash
    {
        static string mensajeAna;

        [STAThread]
        public static void Main()
        {
            // Definimos el mensaje que va a emitir Ana a Jose.
            mensajeAna = "Programando seguridad en C#.NET";

            // Instanciamos el algoritmo acordado por ambos.
            SHA512Managed algoritmoHash = new SHA512Managed();

            byte[] codigoHashAna = GenerarAnaHash(algoritmoHash);

            ComprobarIntegridadMensaje(algoritmoHash, codigoHashAna);
            Console.WriteLine("\nPulse una tecla para finalizar.");
            Console.ReadKey();

            algoritmoHash.Clear();
        }

        /// <summary>
        /// Ana calcula el código hash a partir del mensaje que quiere emitir.
        /// </summary>
        /// <param name="algoritmoHash">
        /// Ambas partes acuerdan previamente usar SHA512.
        /// </param>
        /// <returns>
        /// Array de bytes que representa el código hash calculado.
        /// </returns>
        private static byte[] GenerarAnaHash(SHA512Managed algoritmoHash)
        {
            // Obtenemos un array de bytes a partir de dicho mensaje
            byte[] mensajeBytesArray = Encoding.Default.GetBytes(mensajeAna);
            // Devolvemos el código hash calculado para el mensaje.
            return algoritmoHash.ComputeHash(mensajeBytesArray);
        }

        /// <summary>
        /// Recibe el código hash de Ana y a partir del mensaje emitido por ella
        /// comprueba la integridad del envío.
        /// </summary>
        /// <param name="algoritmoHash">
        /// Ambas partes acuerdan previamente usar SHA512.
        /// </param>
        /// <param name="codigoHash">
        /// Array de bytes que representa el código hash calculado y emitido por Ana.
        /// </param>
        private static void ComprobarIntegridadMensaje(SHA512Managed algoritmoHash, byte[] codigoHashAna)
        {
            // Si quieres hacer fallar la comprobación, descomenta la siguiente línea
            // mensajeAna = mensajeAna + ".";

            // Obtenemos un array de bytes a partir del mensaje que Jose recibe.
            byte[] mensajeBytesArray = Encoding.Default.GetBytes(mensajeAna);
            // Devolvemos el código hash calculado por Jose para el mensaje.
            byte[] codigoHashJose = algoritmoHash.ComputeHash(mensajeBytesArray);

            CompareArrays(codigoHashAna, codigoHashJose);

            // Imprimimos los códigos hash en hexadecimal en la consola
            Console.WriteLine("Este es el código hash de Ana:");
            Console.WriteLine(BitConverter.ToString(codigoHashAna, 0));
            Console.WriteLine("\nEste es el código hash de Jose:");
            Console.WriteLine(BitConverter.ToString(codigoHashJose, 0));
        }

        /// <summary>
        /// Compara dos arrays de bytes
        /// </summary>
        /// <param name="codigoHashAna">
        /// Array de bytes código hash calculado por Ana
        /// </param>
        /// <param name="codigoHashJose">
        /// Array de bytes código hash calculado por Jose
        /// </param>
        private static void CompareArrays(byte[] codigoHashAna, byte[] codigoHashJose)
        {
            // Ahora toca comparar ambos arrays de bytes (los hash calculados por Ana y por Jose)
            // La forma más directa de comparar dos matrices de bytes es recorrerlas,
            // y comparar cada elemento concreto con su homólogo en el segundo valor.
            // Si alguno de los elementos es diferente o las dos matrices no son del mismo tamaño,
            // los dos valores no son iguales. Si no son iguales, es que el mensaje fue alterado.
            bool bEqual = false;
            if (codigoHashAna.Length == codigoHashJose.Length)
            {
                int i = 0;
                while ((i < codigoHashAna.Length) && (codigoHashAna[i] == codigoHashJose[i]))
                {
                    i += 1;
                }
                if (i == codigoHashAna.Length)
                {
                    bEqual = true;
                }
            }
            if (bEqual)
            {
                Console.WriteLine("Los dos valores hash son iguales\n");
            }
            else
            {
                Console.WriteLine("Los dos valores hash no son iguales\n");
            }
        }

        /// <summary>
        /// Otra forma de hacer la comparación
        /// </summary>
        /// <param name="codigoHashAna">
        /// Array de bytes código hash calculado por Ana
        /// </param>
        /// <param name="codigoHashJose">
        /// Array de bytes código hash calculado por Jose
        /// </param>
        private static void CompareArrays(byte[] codigoHashAna, byte[] codigoHashJose)
        {
            if (string.Compare(BitConverter.ToString(codigoHashAna, 0),
                BitConverter.ToString(codigoHashJose, 0)) != 0)
            {
                Console.WriteLine("Los dos valores hash no son iguales\n");
            }
            else
            {
                Console.WriteLine("Los dos valores hash son iguales\n");
            }
        }
    }
}
Anuncios

2 comentarios »

  1. Exelente Articulo !!

    Comentario por Juarez — 19 marzo 2011 @ 4:55 PM | Responder

  2. muy guapo

    Comentario por peri — 31 diciembre 2012 @ 11:37 PM | Responder


RSS feed for comments on this post.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Crea un blog o un sitio web gratuitos con WordPress.com.

A %d blogueros les gusta esto: