Cifrado y descifrado simétrico con Rijndael (AES) utilizando C#/Mono

Introducción.

Advanced Encryption Standard (AES), también conocido como Rijndael (pronunciado “Rain Doll” en inglés), es un esquema de cifrado por bloques adoptado como un estándar de cifrado por el gobierno de los Estados Unidos. El AES fue anunciado por el Instituto Nacional de Estándares y Tecnología (NIST) como FIPS PUB 197 de los Estados Unidos (FIPS 197) el 26 de noviembre de 2001 después de un proceso de estandarización que duró 5 años.  Se transformó en un estándar efectivo el 26 de mayo de 2002. Desde 2006, el AES es uno de los algoritmos más populares usados en criptografía simétrica.

El cifrador fue desarrollado por dos criptólogos belgas, Joan Daemen y Vincent Rijmen, ambos estudiantes de la Katholieke Universiteit Leuven, y enviado al proceso de selección AES bajo el nombre “Rijndael”.

Tomado del artículo Advanced Encryption Standard de Wikipedia.

Implementación con strings.

La aplicación de demostración de esta técnica requiere del uso de por lo menos los siguientes namespaces.

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

Establecer la clave y el vector de inicio.

Estos valores pueden ser especificados manualmente o de manera automática por el framework.  La implementación para permitir que se definan automáticamente estos valores es la siguiente.

Rijndael rijndael = Rijndael.Create();
byte[] key = rijndael.Key;
byte[] iv  = rijndael.IV;

Es posible forzar la generación de nuevas claves y nuevos vectores de inicio para el algoritmo utilizando los métodos rijndael.GenerateKey() y rijndael.GenerateIV() respectivamente.

Si por el contrario se desea especificar estos valores manualmente su implementación es la siguiente siendo strKey y strIv, la clave y el vector de inicialización como cadenas de texto.

byte[] key = UTF8Encoding.UTF8.GetBytes(strKey);
byte[] iv  = UTF8Encoding.UTF8.GetBytes(strIv);

Especificando estos valores manualmente es necesario garantizar que sus longitudes sean válidas para el algoritmo.  En este caso se utilizará una longitud de clave de 32 bits y una longitud de vector de inicio de 16 bits.

int keySize = 32;
int ivSize = 16;

Array.Resize(ref key, keySize);
Array.Resize(ref iv, ivSize);

Cifrado de cadenas de texto.

Para cifrar la información se requiere de los siguientes parámetros.

  1. Cadena de texto con los datos a cifrar.
  2. Clave.
  3. Vector de inicio.

El proceso retornará finalmente una cadena de texto con los datos cifrados.

/**
 * Cifra una cadena texto con el algoritmo de Rijndael
 *
 * @param	plainMessage	mensaje plano (sin cifrar)
 * @param	Key		        clave del cifrado para Rijndael
 * @param	IV		        vector de inicio para Rijndael
 * @return	string		        texto cifrado
 */

public static string encryptString(String plainMessage, byte[] Key, byte[] IV)
{
    // Crear una instancia del algoritmo de Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Establecer un flujo en memoria para el cifrado

    MemoryStream memoryStream = new MemoryStream();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 RijndaelAlg.CreateEncryptor(Key, IV),
                                                 CryptoStreamMode.Write);

    // Obtener la representación en bytes de la información a cifrar

    byte[] plainMessageBytes = UTF8Encoding.UTF8.GetBytes(plainMessage);

    // Cifrar los datos enviándolos al flujo de cifrado

    cryptoStream.Write(plainMessageBytes, 0, plainMessageBytes.Length);

    cryptoStream.FlushFinalBlock();

    // Obtener los datos datos cifrados como un arreglo de bytes

    byte[] cipherMessageBytes = memoryStream.ToArray();

    // Cerrar los flujos utilizados

    memoryStream.Close();
    cryptoStream.Close();

    // Retornar la representación de texto de los datos cifrados

    return Convert.ToBase64String(cipherMessageBytes);
}

Descifrado de cadenas de texto.

El proceso inverso, el de descifrado, se realiza de manera antagónica.  Para hacerlo es necesario contar con los siguientes parámetros.

  1. Cadena de texto con los datos cifrados.
  2. Clave.
  3. Vector de inicio.

El proceso retornará finalmente una cadena de texto con los datos descifrados.

/**
 * Descifra una cadena texto con el algoritmo de Rijndael
 *
 * @param	encryptedMessage	mensaje cifrado
 * @param	Key			clave del cifrado para Rijndael
 * @param	IV			vector de inicio para Rijndael
 * @return	string			texto descifrado (plano)
 */

public static string decryptString(String encryptedMessage, byte[] Key, byte[] IV)
{
    // Obtener la representación en bytes del texto cifrado

    byte[] cipherTextBytes = Convert.FromBase64String(encryptedMessage);

    // Crear un arreglo de bytes para almacenar los datos descifrados

    byte[] plainTextBytes = new byte[cipherTextBytes.Length];

    // Crear una instancia del algoritmo de Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo en memoria con la representación de bytes de la información cifrada

    MemoryStream memoryStream = new MemoryStream(cipherTextBytes);

    // Crear un flujo de descifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 RijndaelAlg.CreateDecryptor(Key, IV),
                                                 CryptoStreamMode.Read);

    // Obtener los datos descifrados obteniéndolos del flujo de descifrado

    int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);

    // Cerrar los flujos utilizados

    memoryStream.Close();
    cryptoStream.Close();

    // Retornar la representación de texto de los datos descifrados

    return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}

Implementación con archivos.

El cifrado y descifrado de mensajes en archivos se realiza de manera similar al expuesto anteriormente con cadenas, sin embargo varían los flujos (streams) utilizados para obtener y dirigir el flujo de la información.

Cifrado a archivos.

/**
 * Cifra una cadena texto con el algoritmo de Rijndael y lo almacena en un archivo
 *
 * @param	plainMessage	mensaje plano (sin cifrar)
 * @param	filename	        nombre del archivo donde se almacenará el mensaje cifrado
 * @param	Key		        clave del cifrado para Rijndael
 * @param	IV		        vector de inicio para Rijndael
 * @return	void
 */

public static void encryptToFile(String plainMessage, String filename, byte[] Key, byte[] IV)
{
    // Crear un flujo para el archivo a generarse

    FileStream fileStream = File.Open(filename, FileMode.OpenOrCreate);

    // Crear una instancia del algoritmo Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(fileStream,
                                                 RijndaelAlg.CreateEncryptor(Key, IV),
                                                 CryptoStreamMode.Write);

    // Crear un flujo de escritura basado en el flujo de cifrado

    StreamWriter streamWriter = new StreamWriter(cryptoStream);

    // Cifrar el mensaje a través del flujo de escritura

    streamWriter.WriteLine(plainMessage);

    // Cerrar los flujos utilizados

    streamWriter.Close();
    cryptoStream.Close();
    fileStream.Close();
}

Descifrado de archivos.

/**
 * Descifra el contenido de un archivo con el algoritmo de Rijndael y lo retorna
 * como una cadena de texto plano
 *
 * @param	filename		nombre del archivo donde se encuentra el mensaje cifrado
 * @param	Key			clave del cifrado para Rijndael
 * @param	IV			vector de inicio para Rijndael
 * @return	string			mensaje descifrado (plano)
 */

public static string decryptFromFile(String filename, byte[] Key, byte[] IV)
{
    // Crear un flujo para el archivo a generarse

    FileStream fileStream = File.Open(filename, FileMode.OpenOrCreate);

    // Crear una instancia del algoritmo Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(fileStream,
                                                 RijndaelAlg.CreateDecryptor(Key, IV),
                                                 CryptoStreamMode.Read);

    // Crear un flujo de lectura basado en el flujo de cifrado

    StreamReader streamReader = new StreamReader(cryptoStream);

    // Descifrar el mensaje a través del flujo de lectura

    string plainMessage = streamReader.ReadLine();

    // Cerrar los flujos utilizados

    streamReader.Close();
    cryptoStream.Close();
    fileStream.Close();

    return plainMessage;
}

Aplicación de demostración.

La aplicación de demostración incluye los conceptos y el código expuestos en este artículo.  Con ella es posible cifrar y descifrar un mensaje que consiste en una cadena de texto arbitraria en memoria y en un archivo.

Aplicación de demostración del uso del algoritmo Rijndael
Aplicación de demostración del uso del algoritmo Rijndael

Construír la aplicación.

La aplicación de demostración puede construírse utilizando la solución incluída en la distribución con MonoDevelop o Visual Studio.  También es posible construírla desde línea de comando (Mono) mediante la siguiente instrucción.

$ gmcs “/out:RijndaelSample.exe” “/r:/usr/lib/mono/2.0/System.dll” /t:exe “RijndaelSample/Main.cs”

Enlaces.

7 thoughts on “Cifrado y descifrado simétrico con Rijndael (AES) utilizando C#/Mono”

  1. El algoritmo no solo está bien explicado si no que además el ejemplo funciona para todos los casos que he probado, de hecho es el único que me está funcionando para toda la muestra.
    Mil gracias de corazón.

  2. Jorge, mil gracias. Y déjame decirte que hace mucho, muchos años que no veía un trabajo tan claro como el tuyo, bien explicado. Simplemente Excelente!
    Este tema lo tenía flojo, pero ahora ya no y gracias a vos.

    Un gran Saludo y otra vez muchas… muchas gracias.

Leave a Reply

Your email address will not be published. Required fields are marked *