Criptografía en .NET, parte I

Fecha: 11-Octubre-2004
Autor: Alden Torres aldenml@yahoo.com

 


Introducción

La criptografía es un mundo fascinante dentro de las matemáticas, pero por la importancia y valor que en nuestros días adquiere la información y la necesidad ineludible de protegerla, la criptografía y en un sentido más amplio, la criptología, trasciende sus fronteras y se adentra en el mundo de la economía, la política y en nuestra vida cotidiana.

Para los programadores que necesitan usar las técnicas criptográficas, usualmente se encuentran ante la problemática, mas allá de qué técnica usar, de que framework usar. El problema se hace más evidente si por ejemplo, se trata de usar Java, en el que hasta hace poco tiempo, el soporte era bien pobre. En el caso de C++, tenemos todo un conjunto de librerías como NTL, LiDIA, PARI, entre otras, pero con estándares de utilización tan dispares, que hace virtualmente imposible la utilización de lo mejor de cada una de ellas. Microsoft aparentemente ha tenido todo esto en cuenta y para nuestro beneficio, brinda todo un namespace System.Security.Cryptography dedicado a la implementación de técnicas criptográficas.

A continuación una exploración rápida de algunas técnicas criptográficas así como ejemplos de su utilización en .NET

Cifrado de claves simétricas

El cifrado de clave simétrica, también conocido como de clave privada, utiliza una única clave tanto para cifrar como para descifrar. Estos métodos de cifrado suelen ser muy eficientes y se usan cuando el volumen de datos es muy grande.

El objetivo principal de todos los algoritmos es hacer una transformación reversible de los datos de entrada, pero cuya transformación sea computacionalmente imposible a menos que se cuenta con una información adicional, a decir en este caso, la misma llave con la que se cifraron los datos.

Veamos un ejemplo simple de su utilización:

public static string DESEncrypt(string message, out string key)
{
    DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    key = Convert.ToBase64String(des.Key);
    des.Mode = CipherMode.ECB;
    ICryptoTransform encryptor = des.CreateEncryptor();
    byte
[] data = Encoding.Unicode.GetBytes(message);
    byte
[] dataEncrypted = encryptor.TransformFinalBlock(data, 0, data.Length);
    return
Convert.ToBase64String(dataEncrypted);
}
public
static string DESDecrypt(string message, string key)
{
    DESCryptoServiceProvider des = new DESCryptoServiceProvider();
    des.Key = Convert.FromBase64String(key);
    des.Mode = CipherMode.ECB;
    ICryptoTransform decryptor = des.CreateDecryptor();
    byte
[] data = Convert.FromBase64String(message);
    byte
[] dataDecrypted = decryptor.TransformFinalBlock(data, 0, data.Length);
    return
Encoding.Unicode.GetString(dataDecrypted);
}

Dejamos al lector la prueba en la practica de los códigos.

Sin embargo, el código expuesto anteriormente carece de utilidad práctica, puesto que con la codificación de cada mensaje, se genera una nueva llave, la cual habría que enviar junto con el mensaje para poder ser descifrado. En la práctica lo que se hace es que las dos partes que se quieren comunicar, se reúnen previamente y comparten una misma llave que mantienen en secreto. Luego, esta misma llave es la que se usa en el cifrado y descifrado de los mensajes que se envían uno a otro. Así nuestra rutina de cifrado quedaría:

public static string DESEncrypt(string message, string key)
{
    des.Key = . . . // convertir key a un arreglo de bytes
  
 . . . // resto de la implementación
}

¡Pero atención! Hemos supuesto que la longitud de la llave pueda ser cualquiera. En realidad esto no es así.

Para conocer estos detalles ( a veces especificados por el algoritmo y otras por la implementación) tenemos lo siguiente:

SymmetricAlgorithm.LegalKeySizes // nos da los tamaños de las llaves soportadas por esta implementación del algoritmo

SymmetricAlgorithm.ValidKeySize // método para determinar si una longitud es soportada por el algoritmo.

Para mejorar nuestra implementación y hacerla más útil en la práctica, vamos a desarrollar primeramente una rutina para generar llaves específicas de un algoritmo con una longitud determinada.

public static string GenerateKey(SymmetricAlgorithm algorithm, int keySize)
{
    if
(algorithm.ValidKeySize(keySize))
    {
        algorithm.KeySize = keySize; algorithm.GenerateKey();
        return
Convert.ToBase64String(algorithm.Key);
    }
    else

        throw
new ArgumentException("Invalid key size");
 }

Se utilizaría: 
key = Algorithms.GenerateKey(new RijndaelManaged(), 256);

 

Ahora estamos en condiciones de tener las nuevas implementaciones para cifrar y descifrar

public static string Encrypt(string message, SymmetricAlgorithm algorithm, string key) {
    algorithm.Key = Convert.FromBase64String(key);
    algorithm.Mode = CipherMode.ECB;
    ICryptoTransform encryptor = algorithm.CreateEncryptor();
    byte
[] data = Encoding.Unicode.GetBytes(message);
    byte
[] dataEncrypted = encryptor.TransformFinalBlock(data, 0, data.Length);

    return
Convert.ToBase64String(dataEncrypted);
 }
public static string Decrypt(string message, SymmetricAlgorithm algorithm, string key) {
    algorithm.Key = Convert.FromBase64String(key);
    algorithm.Mode = CipherMode.ECB;
    ICryptoTransform decryptor = algorithm.CreateDecryptor();
    byte
[] data = Convert.FromBase64String(message);
    byte
[] dataDecrypted = decryptor.TransformFinalBlock(data, 0, data.Length);

    return
Encoding.Unicode.GetString(dataDecrypted);
}

Seguridad de los sistemas simétricos

Usualmente se considera como un parámetro de seguridad el tamaño de la clave utilizada y es que aunque no es exactamente así, en los algoritmos simétricos más utilizados, no se ha podido encontrar una forma de romper el sistema, de forma más eficiente que la simple fuerza bruta. El ataque por fuerza bruta consiste en probar todas las llaves hasta que se logre obtener el texto claro.

De aquí se desprende que es conveniente saber qué tamaños soportan los algoritmos, porque mientras más grande sea la llave, más difícil es de romper el sistema. Veamos los siguientes valores:

AlgoritmoTamaño de Llave
DES64
TripleDES192
Rijndael256

Sin embargo, no quiere decir que la complejidad de romper, por ejemplo, DES con una llave de 64 bits, sea 2^64. En realidad la seguridad efectiva de DES es equivalente a una llave de 56 bits con lo que se puede implementar un algoritmo con una complejidad de 2^56. El sistema DES es descartado para proteger información sensible, debido a la rapidez con que se pueden probar todas las llaves y ver cual es la utilizada. Para Rijndael, cuya historia es muy interesante y que ha desencadeno toda una serie de nuevos ataques, existe un ataque en especial, el XSL, que logra bajar la complejidad a 2^100. Aunque todavía no presentan utilidad práctica (y por lo cual Rijndael sigue siendo considerado seguro) es un punto de atención a tener en cuenta.

Conclusiones

Espero que este sea solo un punto de entrada al fascinante mundo de la criptografía, cuya utilización es de fácil acceso dentro de la plataforma .NET.

En las próximas partes, estaré hablando de los modos de operación de los algoritmos simétricos, el concepto de llave pública, como se utilizan en las firmas digitales y certificados digitales y cómo .NET nos brinda un soporte para su fácil utilización.


ir al índice

Fichero con el código de ejemplo: aldenml_Criptografia_en_dotNET_I.zip - 1 KB