El blog del burgués

6 abril 2010

Firmas Digitales con .NET

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

Los algoritmos asimétricos que adicionalmente tienen funciones de firma y verificación de firmas son los llamados algoritmos de firmas digitales. Lo que hacen es, a partir de un mensaje dado, crear un conjunto de datos adicionales (la firma), la cual es enviada junto con el mensaje a transmitir. La relativa lentitud de los algoritmos asimétricos hace que en realidad lo que se firma no suele ser el mensaje a enviar, sino un código hash criptográfico creado a partir del mensaje, que es equivalente a firmar el mensaje, pero mucho más rápido, porque un código hash son menos datos a procesar. El funcionamiento es el siguiente:

  • Ana acuerda con Jose un algoritmo de firma y crea un nuevo par de llaves.
  • Ana mantiene en secreto la llave privada y envía a Jose la llave pública.
  • Ana crea el mensaje a enviar y el código hash de dicho mensaje.
  • Ana introduce dicho código y la llave privada en la función que crea la firma del mensaje.
  • Ana envía el mensaje junto con la firma creada a Jose.
  • Jose crea su propio código hash a partir del mensaje recibido.
  • Jose introduce dicho código, la llave pública que recibió de Ana en la función que verifica la firma.

Ana es la que crea el par de claves, esa es la diferencia con respecto a la encriptación asimétrica, dónde Jose era el responsable de la creación del par de claves. Ana y Jose deben acordar previamente el uso de un mismo algoritmo de código hash.

Seguridad de las firmas digitales

Las firmas digitales deben ser criptográficamente seguras, deben proveer características de seguridad equivalentes a las de las firmas físicas:

  • Nadie debería ser capaz de falsificar la firma digital de Ana: Todo depende de la gestión y protección de la llave privada.
  • Nadie debería ser capaz de reutilizar la firma digital de Ana: Podría extraerse de un documento ya firmado por Ana y adjuntarla a otro documento diferente que produzca el mismo código hash, pero ya vimos en ésta entrada lo complicado que es hacer eso.
  • Nadie debería ser capaz de alterar un documento firmado: Si se altera el contenido de un documento, la firma digital de Ana ya no será válida, porque el documento alterado producirá un código hash diferente del que Ana firmó.
  • Ana no debería ser capaz de negar que fue ella realmente quien firmó un documento: Se asume que solamente ella conoce la llave privada. Si ella sospecha que la seguridad de su llave privada está comprometida, debe crear otro par de claves nuevas. Ana debe ser la responsable de todas las firmas digitales creadas con su clave privada.

La seguridad de las firmas digitales por tanto, reside en dos puntos clave:

  • Nadie puede obtener la clave privada de Ana.
  • Nadie es capaz de encontrar un documento que produzca un código hash específico.

Ambas cosas podrían suceder, pero debería de llevar mucho tiempo lograrlo. La seguridad de las firmas digitales depende de las leyes de la probabilidad y de una gestión efectiva de las claves privadas.

Los algoritmos de firmas digitales de .NET Framework

.NET Framework incluye dos algoritmos asimétricos que soportan firmas digitales:

  • Algoritmo RSA: Define funciones para la encriptación de datos y las firmas digitales. El par de llaves creadas pueden ser las mismas para ambas tareas.
  • DSA (Digital Signature Algorithm): Soporta firmas digitales pero no encriptación de datos.

El código hash a firmar, creado a partir del documento, normalmente es formateado antes de ser procesado por la función de firma, asegurando que:

  • El par de claves creado rellena el código hash hasta el tamaño máximo que puede ser firmado.
  • Protege a la firma digital frente a ciertos tipos de ataques criptográficos.

El protocolo de formateo más común es PKCS #1, el cual funciona de la siguiente forma:

  • Crear el código hash a partir del documento a firmar.
  • Concatenar el Id del algoritmo hash usado junto con la representación hexadecimal del código hash para formar el valor DigestInfo.
  • Crear un array de bytes de relleno. El valor de cada byte debe ser 0xFF. El número de bytes a crear depende de la longitud de la llave y de la longitud de DigestInfo. El número de bytes es: (Longitud de la llave) – (Longitud de DigestInfo) – 3 (longitudes expresadas en bytes). La longitud de dicho array de relleno será al menos de 8 bytes.
  • Combinar lo siguiente para formar los datos formateados:
    • Un byte 0x00.
    • Un byte 0x01.
    • El array de bytes de relleno.
    • DigestInfo (representación en bytes).

Una vez que ya se han creado los datos formateados, ya se pueden firmar con la función de firma. La siguiente tabla representa el valor que adquiere Id en función del tipo de algoritmo hash:

Programando firmas digitales

La siguiente figura muestra la jerarquía de clases de .NET Framework para trabajar con algoritmos de firmas digitales:

Hay varias formas de realizar operaciones de firmado digital. Las clases que están en azul son clases abstractas y las clases que están en verde son clases de implementación. La clase abstracta System.Security.Cryptography.RSA no provee ningún método de firma digital con el algoritmo RSA. La clase abstracta System.Security.Cryptography.DSA utiliza números aleatorios para crear firmas, lo cual significa que dos firmas serán diferentes, aunque hayan sido creadas a partir de los mismos datos y con el mismo par de claves.

Usando las clases abstractas

El método CreateSignature de la clase System.Security.Cryptography.DSA acepta un código hash SHA-1 que será formateado acorde al protocolo PKCS #1, y firmado como muestra el siguiente ejemplo (se omite el proceso de especificar el par de claves a usar):

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

namespace Encription
{
    class DigitalSignatures
    {
        [STAThread]
        public static void Main()
        {
            // Mensaje a firmar
            byte[] mensaje = Encoding.Default.GetBytes("Programando seguridad con .NET Framework.");

            // Creamos una instancia del algoritmo SHA1 y el código hash
            SHA1 sha1 = SHA1.Create();
            byte[] codigoHash = sha1.ComputeHash(mensaje);

            // Creamos una instancia del algoritmo DSA con la clase abstracta
            DSA dsa = DSA.Create(  );

            // Firmamos el código hash creado a partir del mensaje con el método CreateSignature
            byte[] firmaDigital = dsa.CreateSignature(codigoHash);
        }
    }
}

Vemos como la firma digital creada a partir del mensaje a transmitir ha sido devuelta como un array de bytes. Al código anterior le podríamos añadir la siguiente línea, la cual ilustra como verificar una firma digital:

            // Usamos el método VerifySignature para verificar la firma DSA.
            bool firmaValida = dsa.VerifySignature(codigoHash, firmaDigital);

Usando las clases de implementación

Las clases RSACryptoServiceProvider y DSACryptoServiceProvider definen 4 métodos relativos a las firmas digitales, adicionales a los que acabamos de ver:

El método SignData crea una firma generando un código hash, formatea dicho código hash usando PKCS #1 y firma el resultado. El método VerifyData correspondiente crea el código hash formateado con PKCS #1 y lo usa para verificar la firma. Para el algoritmo RSA, los códigos hash son generados usando una instancia de System.Security.Cryptography.HashAlgorithm, pasada como argumento a los métodos SignData y VerifyData. Para el algoritmo DSA, el algoritmo hash usado siempre es SHA-1. Las siguientes líneas de código demuestran cómo usar los métodos SignData y VerifyData:

        private static void ImplementationClass()
        {
            // Mensaje a firmar
            byte[] mensaje = Encoding.Default.GetBytes("Programando seguridad con .NET Framework.");

            // Crear una instancia de la clase de implementación DSA
            DSACryptoServiceProvider dsa = new DSACryptoServiceProvider();
            // Creamos una firma para el mensaje
            byte[] firmaDSA = dsa.SignData(mensaje);
            // Verificamos la firma usando el mensaje
            bool firmaValida = dsa.VerifyData(mensaje, firmaDSA);

            // Crear una instancia de la clase de implementación RSA
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            // Creamos una instancia del algoritmo SHA1
            HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");
            byte[] firmaRSA = rsa.SignData(mensaje, sha1);
            // Verificamos la firma usando el mensaje
            firmaValida = rsa.VerifyData(mensaje, sha1, firmaRSA);
        }

La principal ventaja del método SignData es que los datos a firmar pueden ser leídos de un stream, lo cual es útil para firmar documentos que de otra forma deberían ser leídos e introducidos en memoria. Las siguientes líneas de código demuestran cómo usar el método SignData para crear una firma DSA leyendo datos de un stream (se asume que debe existir el fichero):

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

namespace Encription
{
    class DigitalSignatures
    {
        [STAThread]
        public static void Main()
        {
            // Abrimos el fichero como un stream
            System.IO.FileStream stream
                = new System.IO.FileStream(@"C:\MisDatos.txt", System.IO.FileMode.Open);

            // Creamos una instancia de la clase implementación DSA
            DSACryptoServiceProvider dsa = new DSACryptoServiceProvider();

            // Creamos una firma digital del stream de datos
            byte[] firmaDigital = dsa.SignData(stream);

            // Liberamos recursos
            stream.Close();
        }
    }
}

Los métodos SignHash y VerifyHash formatean y firman un código hash preexistente, el cual es pasado a dichos métodos como argumento; estos métodos también requieren una cadena como argumento que represente el identificador del algoritmo hash que ha sido usado para crear el código hash. El método estático MapNameToOID de la clase CryptoConfig devuelve el Id de un algoritmo hash, así que las siguientes líneas de código obtienen el Id para el algoritmo SHA-1:

            string id = CryptoConfig.MapNameToOID("SHA1");
            Console.WriteLine(id);
            Console.ReadKey();

Las siguientes sentencias demuestran cómo crear y verificar una firma usando los métodos SignHash y VerifyHash y el algoritmo DSA. Dese cuenta que la implementación DSA requiere el Id del algoritmo hash, aunque la especificación DSA demande que SHA-1 sea siempre usado:

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

namespace Encription
{
    class DigitalSignatures
    {
        [STAThread]
        public static void Main()
        {
            // Mensaje a firmar
            byte[] mensaje = Encoding.Default.GetBytes("Programando seguridad con .NET Framework.");

            // Creando el código hash para el mensaje, usando SHA-1
            byte[] codigoHash = HashAlgorithm.Create("SHA1").ComputeHash(mensaje);

            // Crear una instancia de la clase de implementación DSA
            DSACryptoServiceProvider dsa = new DSACryptoServiceProvider();

            // Creamos una firma DSA usando el código hash.
            byte[] firmaDSA = dsa.SignHash(codigoHash, CryptoConfig.MapNameToOID("SHA1"));

            // Verificamos la firma
            bool firmaValida = dsa.VerifyHash(codigoHash, CryptoConfig.MapNameToOID("SHA1"), firmaDSA);
        }
    }
}

Usando las clases Signature Formatter y Deformatter

.NET Framework posee las clases que se muestran en la figura más abajo. Estas clases lo que hacen es formatear un código hash usando PKCS #1, y firman el resultado mediante las clases abstractas y las clases de implementación. La funcionalidad que las clases formatter y deformatter proporcionan es equivalente a usar los métodos que hemos ido viendo a lo largo de esta entrada, así que no hay ninguna ventaja específica al usar las clases formatter y deformatter:

La clase abstracta AsymmetricSignatureFormatter define los métodos que se especifican en la siguiente tabla:

Ésta clase abstracta no especifica cómo dar formato a un código hash, así que las clases de implementación pueden definir el formato apropiado para un algoritmo. Ambas clases de implementación usan PKCS #1. La clase abstacta RSAPKCS1SignatureFormatter define los métodos que se especifican en la siguiente tabla:

Las siguientes sentencias ilustran cómo usar ambas clases:

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

namespace Encription
{
    class DigitalSignatures
    {
        [STAThread]
        public static void Main()
        {
            // Mensaje a firmar
            byte[] mensaje = Encoding.Default.GetBytes("Programando seguridad con .NET Framework.");

            // Creando el código hash para el mensaje, usando SHA-1
            byte[] codigoHash = HashAlgorithm.Create("SHA1").ComputeHash(mensaje);

            // Crear una instancia de la clase de implementación DSA
            DSACryptoServiceProvider dsa = new DSACryptoServiceProvider();

            // Creamos el formateador de firmas
            DSASignatureFormatter formateador = new DSASignatureFormatter();

            // Establecemos la instancia del algoritmo DSA que firmará los datos.
            formateador.SetKey(dsa);

            // Establecemos el nombre del algoritmo hash que será usado para crear el código hash.
            formateador.SetHashAlgorithm("SHA1");

            // Creamos la firma DSA formateada
            byte[] firma = formateador.CreateSignature(codigoHash);

            // Creamos el desformateador de firmas
            DSASignatureDeformatter desFormateador = new DSASignatureDeformatter();

            // Establecemos la instancia del algoritmo DSA que verificará la firma.
            desFormateador.SetKey(dsa);

            // Establecemos el mismo algoritmo hash que se usó para crear el código hash.
            desFormateador.SetHashAlgorithm("SHA1");

            // Verificamos la firma DSA.
            bool firmaValida = desFormateador.VerifySignature(codigoHash, firma);
        }
    }
}
Anuncios

Dejar un comentario »

Aún no hay comentarios.

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: