El blog del burgués

13 marzo 2010

Encriptación Simétrica con C#.NET

Filed under: C# — elburgues @ 8:58 AM

Cuando dos partes remotas intercambian datos a  través de un canal inseguro (como por ejemplo internet), ambas partes deben garantizar que los datos comunicados:

  • no puedan ser comprendidos por nadie que pueda estar escuchando: Confidencialidad.
  • no han sido modificados durante la transmisión: Integridad.
  • provienen realmente de quien provienen y no de nadie que haya suplantado la identidad de una de las dos partes: Autenticación.

La criptografía se utiliza para lograr estos objetivos. Para alcanzarlos es necesaria la combinación de una serie de primitivas criptográficas. Aunque este tema da la sensación de ser complejo, en realidad, no es necesario saber cómo funcionan los algoritmos de encriptación. El truco es saber usarlos sin más y evitar malas prácticas.

Consejo: Cualquier algoritmo que cambie de cierta manera un texto no se puede considerar un algoritmo encriptador, sino simplemente “transformador” (por ejemplo, aplicar un XOR a una cadena de texto produce un resultado que, como no lo entendemos, creemos que la información está encriptada, cuando en realidad un hacker podría obtener la información fácilmente). Así que, si vamos a proteger información, debe hacerse con algoritmos probados.

1ª Primitiva criptográfica: La encriptación simétrica o cifrado de clave secreta

En la encriptación simétrica se utiliza una única clave secreta para cifrar y descifrar los datos (de ahí la denominación de simétrica). Son algoritmos muy rápidos, por eso se utilizan para grandes secuencias de datos.

Dentro de los algoritmos de encriptación simétrica podemos encontrar los siguientes (algunos más seguros que otros)

  • DES ( Digital Encryption Standard )
  • 3DES ( Three DES o Triple DES )
  • IDEA ( International Data Encryption Algorithm )
  • AES ( Advanced Encryption Standard )

AES, también conocido como Rijndael (aunque no son exactamente lo mismo), es de amplia aceptación a nivel mundial. Hoy en día es el más seguro y rápido.

Cualquiera de estos algoritmos utiliza los siguientes dos elementos (ninguno de los dos debe pasarse por alto ni subestimarse su importancia):

  • · IV (Vector de inicialización)

No se puede encriptar sin él. Es de 16 bytes de longitud para el algoritmo de Rijndael. No es una 2ª llave, por lo tanto, no se trata de una dato que haya que esconder, únicamente hay que considerar que hay que usar el mismo IV para encriptar/desencriptar un mensaje concreto. Un error común es utilizar el mismo vector de inicialización en todas las encriptaciones. Utilizar siempre un mismo IV es equivalente en seguridad a no utilizar encriptación.

  • · Key (llave)

Esta es la principal información para encriptar/desencriptar en los algoritmos simétricos. Toda la seguridad de un sistema simétrico depende de dónde esté esta llave, cómo esté compuesta y quién tiene acceso. Éste es un dato que debe conocerse única y exclusivamente por los interlocutores de la comunicación. De otra forma, la seguridad en la comunicación se vería comprometida.

Ejemplo de funcionamiento

  • Ana y Jose acuerdan utilizar un algoritmo en particular con una clave y vector de inicialización concretos.
  • Ana redacta un mensaje y cifra el texto usando la clave y el vector de inicialización y se lo envía a Jose por Internet.
  • Jose recibe el texto cifrado y lo descifra utilizando la clave y el vector acordados anteriormente.

Si se intercepta la transmisión, el interceptor no podrá recuperar el mensaje original porque no conoce la clave ni el vector.

Consideraciones acerca de la clave

Cuanto más grande sea el tamaño de la llave, más difícil será obtenerla mediante un ataque por fuerza bruta. Para el algoritmo de Rijndael, las claves pueden ser de 128, 192 y 256 bits de longitud. Por ejemplo, para una clave de 64 bits de longitud, a un ritmo de comprobación de 50 claves por segundo, podría llevar unos 11,6 billones de años en comprobar todos los valores posibles de la clave (unas 750 veces la edad de la tierra), aunque también hay que considerar que, comprobados la mitad de los valores posibles de la clave, existen un 50% de probabilidades de haberla encontrado ya. Por tanto, el éxito de un ataque por fuerza bruta o el nivel de seguridad que ofrece una clave, están sujetos a las leyes de la probabilidad y considerar que un atacante debería comprobar todos los valores posibles de una clave para poder encontrarla es una suposición demasiado optimista. No se puede afirmar categóricamente que una tercera parte que intercepte un mensaje cifrado simétricamente en un canal de comunicaciones inseguro no sea capaz nunca de encontrar la clave secreta. Entonces, la seguridad de un algoritmo simétrico debe basarse en utilizar una clave lo suficientemente bien formada como para que, independientemente del ritmo de comprobación de claves que un atacante pudiera mantener, cuando ésta sea descubierta, los datos que se protegen hayan quedado ya obsoletos.

Consideraciones acerca del vector de inicialización

En un esquema de cifrado por bloques (como Rijndael), la secuencia de texto sin cifrar se parte en bloques para su procesamiento. Para una clave secreta determinada, un cifrado que no utilice un vector de inicialización codificará el mismo bloque de entrada de texto sin cifrar en el mismo bloque de salida de texto cifrado. Si hay bloques duplicados dentro la secuencia de texto a cifrar, habrá bloques duplicados en la secuencia de texto cifrado. Si el hacker sabe algo acerca de la estructura de un bloque del texto sin cifrar, puede utilizar esa información para descifrar el bloque de texto cifrado conocido y, posiblemente, recuperar la clave.

Para combatir este problema, la información del bloque anterior se mezcla en el proceso de cifrado del bloque siguiente (como vamos a ver ahora, en el modo de cifrado ECB – Electronic Code Book Mode no se hace eso). Así pues, el resultado de dos bloques idénticos de texto sin cifrar es distinto. Como esta técnica utiliza el bloque anterior para cifrar el bloque siguiente, se utiliza un IV para cifrar el primer bloque de datos. Con este sistema, los encabezados de los mensajes comunes que un hacker podría conocer no pueden utilizarse para aplicar técnicas de ingeniería inversa en una clave.

Consideraciones acerca del modo de cifrado

Los algoritmos de cifrado de bloque como DES o AES separan el mensaje en bloques de tamaño fijo para su procesamiento, por ejemplo 128 bits. La forma en que se gestionan estos bloques se denomina “modo de cifrado”. Los modos de cifrado que soporta .NET Framework son ECB, CBC, y CFB (es preferible usar CBC).

  • ECB – Electronic Code Book Mode: Las desventajas de este modo de cifrado son grandes, por lo que se usa cada vez menos (hace posible los ataques de diccionario).
  • CBC – Cipher Block Chaining Mode: Es una extensión de ECB que añade cierta seguridad (usa un vector de inicialización IV). Es el modo de cifrado por bloques más usado.

Rijndael permite elegir la longitud de la clave y del bloque individualmente, en el rango de valores {128, 160, 192, 224 y 256 bits}. En realidad, dichas longitudes no tienen por qué coincidir, pero la Publicación oficial del algoritmo AES (FIPS 197) especifica que en AES, la longitud de bloque debe ser siempre 128 bits y que la de la clave puede ser 128, 192, o 256 bits (160 y 224 no son opciones soportadas).

Modos de relleno

La mayoría de los mensajes de texto a cifrar no contienen los bytes necesarios para rellenar totalmente los bloques. A menudo, no hay bytes suficientes para rellenar el último bloque. Cuando esto sucede, se agrega una cadena de relleno al texto. Por ejemplo, si la longitud del bloque es de 64 bits y el último bloque sólo contiene 40 bits, se agregan 24 bits de relleno. .NET Framework soporta dos modos de relleno:

  • Zeros: La cadena de relleno consta de bytes establecidos en cero.
  • PKCS7: La cadena de relleno PKCS #7 consta de una secuencia de bytes, en la que cada byte es igual al número total de bytes de relleno agregados.

Por ejemplo, para una longitud de bloque de 8 bytes, una longitud de datos de 9 bytes, un número de octetos de relleno igual a 7 bytes y los datos iguales a FF FF FF FF FF FF FF FF FF, quedaría:

  • Relleno de Zeros: FF FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00
  • Relleno de PKCS7: FF FF FF FF FF FF FF FF FF 07 07 07 07 07 07 07

Programando encriptación simétrica con C#

Bueno, aquí dejo un ejemplo que ilustra un poco la manera de manejar todo lo que hemos hablado acerca de los algoritmos simétricos, con C#.NET:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class miEjemplo
{
  static void Main()
  {
     // Este es el mensaje que vamos a encriptar.
    string mensaje = “Programando seguridad en C#.NET”;
    Console.WriteLine(“Esto es el mensaje sin cifrar: ” + mensaje);
    Console.WriteLine(“Pulse una tecla para continuar…\n”);
    Console.ReadKey();
    // Creamos el algoritmo encriptador
    SymmetricAlgorithm algoritmo = SymmetricAlgorithm.Create(“Rijndael”);
    //Se podría haber creado el algoritmo de esta otra manera:
    //RijndaelManaged algoritmoEncriptador = new RijndaelManaged();
    ConfigurarAlgoritmo(algoritmo);
    GenerarClave(algoritmo);
    GenerarIV(algoritmo);
    byte[] mensajeEncriptado = Encriptar(mensaje, algoritmo);
    Console.WriteLine(“Esto es el mensaje cifrado:”);
    foreach (byte b in mensajeEncriptado)
    {
      Console.Write(“{0:X2} “, b);
    }
    Console.WriteLine(“\nPulse una tecla para continuar…\n”);
    Console.ReadKey();
    byte[] mensajeDesencriptado = Desencriptar(mensajeEncriptado, algoritmo);
    string mensajeDescrifrado = Encoding.UTF8.GetString(mensajeDesencriptado);
    Console.WriteLine(“Esto es el mensaje descifrado: ” + mensajeDescrifrado);
    Console.WriteLine(“Pulse una tecla para terminar…\n”);
    Console.ReadKey();
    algoritmo.Clear();
  }
  /// <summary>
  /// Configuración del algoritmo simétrico
  /// </summary>
  /// <param name=”algoritmo”>
  /// Una instancia del algoritmo simétrico.
  /// </param>
  private static void ConfigurarAlgoritmo(SymmetricAlgorithm algoritmo)
  {
    // Cambiamos el valor del tamaño de bloque
    algoritmo.BlockSize = 128;
    // Establecemos el modo de cifrado y con el modo de relleno
    algoritmo.Mode = CipherMode.CBC;
    algoritmo.Padding = PaddingMode.PKCS7;
    Console.WriteLine(“Longitud de bloque: {0}”, algoritmo.BlockSize);
    Console.WriteLine(“Modo de cifrado: {0}”, algoritmo.Mode);
    Console.WriteLine(“Modo de relleno: {0}”, algoritmo.Padding);
    Console.WriteLine(“Pulse una tecla para continuar…\n”);
    Console.ReadKey();
  }
  /// <summary>
  /// Tres formas de generar una clave.
  /// </summary>
  /// <param name=”algoritmo”>
  /// Una instancia del algoritmo simétrico.
  /// </param>
  private static void GenerarClave(SymmetricAlgorithm algoritmo)
  {
    // Establecemos la longitud que queremos que tenga la clave a generar.
    algoritmo.KeySize = 256;
    Console.WriteLine(“Longitud de la clave:   {0}”, algoritmo.KeySize);
    Console.WriteLine(“Pulse una tecla para continuar…\n”);
    Console.ReadKey();
    // Leer sin más el valor de la clave hara que se genere.
    // sacamos la clave por consola
    Console.WriteLine(“La clave: “);
    foreach (byte b in algoritmo.Key)
    {
    Console.Write(“{0:X2} “, b);
    }
    Console.WriteLine(“\nPulse una tecla para continuar…\n”);
    Console.ReadKey();
    // Podemos generar otra nueva
    algoritmo.GenerateKey();
    // sacamos la nueva clave por consola
    Console.WriteLine(“Otra clave: “);
    foreach (byte b in algoritmo.Key)
    {
    Console.Write(“{0:X2} “, b);
    }
    Console.WriteLine(“\nPulse una tecla para continuar…\n”);
    Console.ReadKey();
    // Otra forma de crear claves sería con RNG (Random Number Generator)
    RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create();
    // Se rellena el array de bytes de la clave con datos aleatorios
    randomNumberGenerator.GetBytes(algoritmo.Key);
    // sacamos la clave por consola
    Console.WriteLine(“Otra forma de obtener una clave: “);
    foreach (byte b in algoritmo.Key)
    {
    Console.Write(“{0:X2} “, b);
    }
    Console.WriteLine(“\nPulse una tecla para continuar…\n”);
    Console.ReadKey();
  }
  /// <summary>
  /// Para generar un vector de inicialización
  /// </summary>
  /// <param name=”algoritmo”>
  /// Una instancia del algoritmo simétrico.
  /// </param>
  private static void GenerarIV(SymmetricAlgorithm algoritmo)
  {
    // Si haces lo siguiente se genera un nuevo IV
    algoritmo.GenerateIV();
    // sacamos el IV por consola
    Console.WriteLine(“IV (Vector de inicialización): “);
    foreach (byte b in algoritmo.IV)
    {
    Console.Write(“{0:X2} “, b);
    }
    Console.WriteLine(“\nPulse una tecla para continuar…\n”);
    Console.ReadKey();
  }
  /// <summary>
  /// Encripta un mensaje
  /// </summary>
  /// <param name=”mensajeSinEncriptar”>
  /// Mensaje que va a ser encriptado
  /// </param>
  /// <param name=”algoritmo”>
  /// Instancia del algoritmo simétrico a usar para la encriptación
  /// </param>
  /// <returns>
  /// Un array de bytes que representan el mensaje encriptado.
  /// </returns>
  public static byte[] Encriptar(string mensajeSinEncriptar, SymmetricAlgorithm algoritmo)
  {
    // La clase SymmetricAlgorithm delega el proceso de encriptación de datos
    // a la interfaz ICryptoTransform, la cual expone los detalles en el manejo de bloques.
    // Una instancia de ICryptoTransform transforma texto plano en texto cifrado o vice versa.
    // Las siguiente sentencia demuestra como crear transformaciones usando CreateEncryptor.
    // Crear una ICryptoTransform que puede ser usada para encriptar datos
    ICryptoTransform encriptador = algoritmo.CreateEncryptor();
    // Las instancias de la interfaz ICryptoTransform no son útiles en si mismas.
    // .NET framework provee la clase CryptoStream para el manejo de instancias de la interfaz ICryptoTransform.
    // La clase CryptoStream actua como un envoltorio sobre un stream y transforma
    // automáticamente bloques de datos usando una interfaz ICryptoTransform.
    // La clase CryptoStream transforma datos leídos de un stream
    // (por ejemplo, desencriptando texto cifrado de un fichero)
    // o escribiendo en un stream (por ejemplo, encriptando datos generados por programa
    // y almacenando el resultado en un fichero).
    // Crear instancias de la clase CryptoStream requiere un stream real,
    // una instancia de la interfaz ICryptoTransform
    // y un valor de la enumeracion CryptoStreamMode
    // Obtenemos los bytes que representan el mensaje a encriptar
    byte[] textoPlano = Encoding.Default.GetBytes(mensajeSinEncriptar);
    // Creamos un MemoryStream
    MemoryStream memoryStream = new MemoryStream();
    // Cualquier operación de encriptación/desencriptación hara que la clase
    // que implemente el algoritmo simétrico genere una nueva clave e IV
    // si dichos valores no han sido establecidos
    // Creamos el CryptoStream
    CryptoStream cryptoStream = new CryptoStream(memoryStream, encriptador, CryptoStreamMode.Write);
    // Escribimos el textoPlano hacia el CryptoStream
    cryptoStream.Write(textoPlano, 0, textoPlano.Length);
    // Terminamos la operación de encriptación.
    cryptoStream.FlushFinalBlock();
    // Liberamos.
    memoryStream.Close();
    cryptoStream.Close();
    // Obtenemos el texto cifrado del MemoryStream
  return memoryStream.ToArray();
  }
  /// <summary>
  /// Desencripta un mensaje
  /// </summary>
  /// <param name=”mensajeEncriptado”>
  /// Mensaje que va a ser desencriptado
  /// </param>
  /// <param name=”algoritmo”>
  /// Instancia del algoritmo simétrico a usar para la desencriptación
  /// </param>
  /// <returns>
  /// Un array de bytes que representan el mensaje encriptado.
  /// </returns>
  public static byte[] Desencriptar(byte[] mensajeEncriptado, SymmetricAlgorithm algoritmo)
  {
    int numeroBytesDesencriptados = 0;
    // La clase SymmetricAlgorithm delega el proceso de desencriptación de datos
    // Una instancia de ICryptoTransform transforma texto plano en texto cifrado o vice versa.
    // Las siguiente sentencia demuestra como crear transformaciones usando CreateDecryptor.
    byte[] mensajeDesencriptado = new byte[mensajeEncriptado.Length];
    // Crear una ICryptoTransform que puede ser usada para desencriptar datos
    ICryptoTransform desencriptador = algoritmo.CreateDecryptor();
    // Procedemos a descifrar el mensaje
    MemoryStream memoryStream = new MemoryStream(mensajeEncriptado);
    // Creamos el CryptoStream
    CryptoStream cryptoStream = new CryptoStream(memoryStream, desencriptador, CryptoStreamMode.Read);
    // Decrypting data and get the count of plain text bytes.
    numeroBytesDesencriptados = cryptoStream.Read(mensajeDesencriptado, 0, mensajeDesencriptado.Length);
    // Liberamos recursos.
    memoryStream.Close();
    cryptoStream.Close();
    return mensajeDesencriptado;
  }
}
Anuncios

4 comentarios »

  1. Te veo con ganas de ocultar algo… ¿qué será? 😀

    Comentario por Lobosoft — 13 marzo 2010 @ 9:01 PM | Responder

  2. es posible utilizar el codigo en un proyecto de aplicacion web con 2003?? xq el compilador me dice q System.Console no contiene una definicion para ReadKey.- Agradezco mucho tu aporte.-

    Comentario por MONAKO — 18 mayo 2010 @ 4:49 PM | Responder

    • El código lo probé con Visual Studio 2008. Por lo que dices, se ve que no. De todas formas, System.Console.ReadKey() lo único que hace es esperar a que el usuario pulse cualquier tecla para continuar, para probar el código, puedes sustituirlo por otra cosa o comentarlo…

      Comentario por elburgues — 18 mayo 2010 @ 8:09 PM | Responder

  3. Muy buena la aportación. Ya lo estoy probando.

    Comentario por Manolo — 19 octubre 2010 @ 7:15 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: