Desmitificando la Encriptación (Parte I)

Por Patrick Mac Kay Tepper

Bb972216.downlanim(es-es,MSDN.10).gif Descargar ejemplos de este artículo (20 KB).

Contenido

 Introducción
 Agradecimientos
 Introducción, alcance y preguntas iniciales
 Definición de algoritmos
 Malas prácticas o transformaciones
 Encriptación simétrica
 Conclusión

Introducción

Cuando es necesario darle seguridad a ciertos datos de nuestra aplicación, y buscamos información en el Web que nos permita lograrlo, es normal que hablemos (o leamos) de encriptación, y que irremediablemente salgan a la luz palabras tales como algoritmos simétricos, asimétricos y Hash. Estos términos pueden generar temor o dar la sensación de ser complejos. La motivación de este documento es mostrar qué es la encriptación sin necesidad de profundizar en cómo funciona un algoritmo internamente; y también indicar las malas prácticas que se realizan popularmente. Se revisará además la forma correcta de utilizar los algoritmos de encriptación, dónde se deben usar y dónde no.

Debido a lo extenso del tema en cuestión y a que es preferible realizar todas las explicaciones correspondientes, fue necesario dividir el documento en dos partes. Lo que contendrá cada parte está detallado en el siguiente índice.

Agradecimientos

Antes de comenzar deseo agradecer tanto el apoyo de Mike Giagnocavo como el de la información disponible en su blog (http://www.atrevido.net/). Sin sus consejos y correcciones, los ejemplos de este documento y la demostración no habrían quedado de excelente calidad y listos para utilizar.

Introducción, alcance y preguntas iniciales

La seguridad de los datos de cualquier sistema es algo muy importante, y no siempre recibe la atención y dedicación necesarias. Podemos formular las siguientes preguntas básicas y es probable que encontremos falencias de seguridad que pueden comprometer a nuestro sistema:

  • ¿Se está almacenando la información crítica de forma segura?

  • ¿Qué entendemos por seguro?

  • Si se está encriptando, ¿Qué algoritmo se está utilizando?

  • ¿Se permite la desencriptación de datos que no debieran poder desencriptarse?

  • ¿Cómo está realizándose la encriptación?

Adicionalmente, podemos complementar con preguntas más complejas:

  • ¿Dónde están almacenadas la o las llaves?

  • ¿Con qué frecuencia se cambian las llaves?

  • ¿Cómo se enfrenta el problema de cambiar las llaves con tanta frecuencia?

  • ¿Con qué criterio se definen las nuevas llaves?

Es probable que algunas de las preguntas básicas no sean respondidas adecuadamente. Si esto es así, sería recomendable hacer una revisión de los criterios y metodologías que se están utilizando, y determinar si es necesario hacer modificaciones o rehacer esas funcionalidades. Si el pensamiento es que esto no genera utilidad, cabe hacerse las siguientes preguntas: ¿Qué le sucedería al negocio si alguien accediese a cualquiera de los datos privados del sistema? ¿Se verá comprometida la seguridad con hechos como éste? De seguro, en ese momento el pensamiento sería que valdría la pena. Entonces, manos a la obra.

El alcance de este documento es dar a conocer los distintos métodos que existen para proteger tanto el almacenamiento como el envío de la información. Este documento NO tiene como intención ser un manual de seguridad. Esto es lo mínimo que debe tener una organización para no estar al descubierto en sus procesos. Se debe entender que existen libros dedicados a la seguridad. Aquí se está danto el puntapié inicial para salir del desconocimiento que existe sobre este tema. Es responsabilidad tanto de los administradores  de sistemas operativos como del arquitecto de soluciones definir las reglas, dónde almacenar sus llaves, los criterios de cambio de llaves y también los lugares donde almacenará la información.

Habiendo introducido el tema y definido el alcance, comencemos con la revisión de los algoritmos y los distintos tipos que existen. 

Definición de algoritmos

Como se mencionó con anterioridad, existen distintos tipos de algoritmos de encriptación, pero algo que no se ha mencionado hasta ahora son los “algoritmos” que se cree que encriptan, pero que no hacen nada más que transformar texto (o información). El objetivo de hacer esta distinción es mostrar algunas malas prácticas que se cometen, pensando que cualquier algoritmo que cambia de cierta forma un texto, está realizando encriptación de datos. Estos algoritmos podremos llamarlos simplemente "Transformaciones".

Como se está hablando de encriptación, se revisarán también los algoritmos que han sido construidos con distintos fines y que están agrupados en distintas categorías. ¿Cuál es la funcionalidad de los algoritmos dentro de cada uno de estos grupos? Cada uno tiene un objetivo y un problema que atacar, y por lo mismo hay casos en los que debe utilizarse uno y sólo uno de estos algoritmos, como también casos en los que debe utilizarse algoritmos de más de un grupo.

Antes de revisar los escenarios, se debe aclarar el concepto de llave. La llave de encriptación es una serie de carácteres, de determinado largo, que se utiliza para encriptar y desencriptar la información que se quiere proteger. El largo de la llave depende del algoritmo. Existen llaves privadas y públicas, que serán detalladas más adelante. Veamos los siguientes escenarios.

  • Escenario 1: Necesito almacenar información crítica que deberá poder descifrarse, y seré yo el único que haga todo el proceso. Nadie más tendrá acceso a la llave con que se encriptará y desencriptará la información.

  • Escenario 2: Necesito que me envíen información crítica que yo desencriptaré posteriormente, pero necesito que las personas que me van a enviar información pueden encriptarla libremente, pero no desencriptarla. En este caso, se deja disponible una llave pública para que ellos encripten y yo tendré mi llave privada de encriptación en forma segura.

  • Escenario 3: Necesito almacenar o enviar información crítica de forma segura, pero que no requerirá ser desencriptada para su validación, o que es extremadamente importante verificar que no haya sido modificada en el camino.

Estos son los tres escenarios más comunes. Es probable que te preguntes para qué puede haber casos como el 3. Lo veremos más adelante con ejemplos, destacando ventajas y desventajas. Los tres escenarios calzan con los algoritmos listados a continuación:

  • Escenario 1: Encriptación simétrica

  • Escenario 2: Encriptación asimétrica

  • Escenario 3: Hash de información

Antes de comenzar a revisar cada uno de estos algoritmos, revisemos una muy mala práctica que se utiliza más de lo que debiera, que corresponde a la utilización de “algoritmos” o mejor dicho, transformaciones del contenido.

Malas prácticas o transformaciones

Antes mencionamos que es común escuchar o ver que se utilizan ciertas transformaciones como métodos de protección. Como la comparación se hace con el ojo humano, si se tiene una cadena de texto y se decide transformar aplicándole un XOR o desplazando los bytes, el resultado de esa transformación nos produce el efecto de que está encriptado, por que no lo podemos entender (nuestro cerebro no lo entiende). Debido a esto, no vamos a hacer el ejemplo transformando texto sino que lo haremos transformando una imagen. Se aplicarán los dos efectos mencionados recién, XOR y Desplazamiento, y se verá que es efectivamente lo que hacen. El motivo de utilizar una imagen es para que nuestro ojo no se engañe con las apariencias.

Nuestro ejemplo nos presenta una pantalla como la que se muestra en la Figura 1, donde tenemos dos botones; uno para cargar la imagen, otro para aplicarle las transformaciones XOR, y otro para desplazar los bytes:

Bb972216.art216-img01-482x392(es-es,MSDN.10).jpg
Figura 1. Volver al texto.

Si se presiona Transformar, el resultado será el que se muestra en la Figura 2:

Bb972216.art216-img02-482-392(es-es,MSDN.10).jpg
Figura 2: Resultado de las transformaciones. Volver al texto.

Como se puede apreciar, no se produce una encriptación sino sólo una transformación de los datos (más adelante verás cómo queda la imagen realmente encriptada). Este tipo de transformaciones no protege nuestra información. Hagamos ahora las siguientes preguntas: ¿Cuánta seguridad existe con estas transformaciones? ¿Cuánto demorará un hacker en retransformar esto? El hacker obtiene la respuesta en menos de lo que te toma a ti leer sólo esta línea. Hacerle modificaciones a este “algoritmo” no sirve. La encriptación no es trivial de hacer, pero es mucho más fácil de usar. Más adelante conocerás  la cantidad de años que estuvo estudiándose el algoritmo Rijndael o AES, para ser declarado vencedor.

El código que utilizamos para realizar la transformación XOR es el siguiente. El código de desplazamiento es muy similar. Este, al igual que todo el código del documento, está disponible en el archivo adjunto con este artículo. Por motivos que no corresponde explicar aquí, ya que no entran en el objetivo del documento, con la imagen que se está trabajando es necesario comenzar desde la posición 34, ya que si se comienza desde el byte 0 transformando, el archivo BMP ya no es posible visualizarlo.

Lo único que hace este “algoritmo” es aplicar XOR a un arreglo de bytes. Para aplicarlo sobre texto lo único que hay que hacer son las transformaciones de bytes a texto y viceversa. Como explicación sencilla de este proceso, podemos decir que lo que hace es tomar cada uno de los bytes y aplicarle un XOR (representado por el símbolo "^" en el código). Un XOR corresponde al resultado de la comparación bit a bit de dos bytes. En este caso, un byte viene del texto a transformar y el otro del número 87 elegido al azar.

public class TransformacionXOR
{
       private static int _XOR = 87;
 
       public static byte[] Codificar(byte[] bytACodificar)
       {
              for (int _intPos = 34; _intPos < bytACodificar.Length; _intPos++)
                     bytACodificar[_intPos] = (byte) (_XOR ^ bytACodificar[_intPos]);
              return bytACodificar; 
       }
 
       public static byte[] DeCodificar(byte[] bytADecodificar)
       {
              for (int _intPos = 34; _intPos < bytADecodificar.Length; _intPos++)
                     bytADecodificar[_intPos] = (byte) (_XOR ^ bytADecodificar[_intPos]); 
              return bytADecodificar;
       }
}

Como ven, este algoritmo tan lineal no nos da ninguna seguridad; y por lo mismo, jamás debe utilizarse para proteger información. No debe creerse que es XOR el responsable de esto. XOR es muy útil en otros contextos, pero no se puede utilizar de esta forma.

Nota: Antes de continuar, es muy importante aclarar lo siguiente. Si se va a proteger información, el proceso DEBE realizarse a conciencia y con algoritmos probados. Además, se debe utilizar el algoritmo de la forma en que fue concebido. Implementaciones a medias, son tan inútiles como las transformaciones recién vistas o como la no utilización de ésta. O el proceso se hace bien, o no se hace.

Veamos ahora los algoritmos reales de encriptación.

Encriptación simétrica

Cuando hablamos de encriptación y no de transformación, ya estamos adentrándonos en temas de mayor protección, de algoritmos conocidos y seguridad real. El proceso de realizar una encriptación es complejo para ser entendido por nosotros mismos, pero no es limitante para conocer cuáles son los pasos para utilizarlos y qué errores no se deben cometer.

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

DES ( Digital Encryption Standard )

Creado en 1975 con ayuda de la NSA (National Security Agency); en 1982 se convirtió en un estándar. Utiliza una llave de 56 bit. En 1999 logró ser quebrado (violado) en menos de 24 horas por un servidor dedicado a eso. Esto lo calificó como un algoritmo inseguro y con falencias reconocidas.

3DES ( Three DES o Triple DES )

Antes de ser quebrado el DES, ya se trabajaba en un nuevo algoritmo basado en el anterior. Este funciona aplicando tres veces el proceso con tres llaves diferentes de 56 bits. La importancia de esto es que si alguien puede descifrar una llave, es casi imposible poder descifrar las tres y utilizarlas en el orden adecuado. Hoy en día es uno de los algoritmos simétricos más seguros.

IDEA ( International Data Encryption Algorithm )

Más conocido como un componente de PGP (encriptación de mails), trabaja con llaves de 128 bits. Realiza procesos de shift y copiado y pegado de los 128 bits, dejando un total de 52 sub llaves de 16 bits cada una. Es un algoritmo más rápido que el DES, pero al ser nuevo, aún no es aceptado como un estándar, aunque no se le han encontrado debilidades aún.

AES ( Advanced Encryption Standard )

Este fue el ganador del primer concurso de algoritmos de encriptación realizado por la NIST (National Institute of Standards and Technology) en 1997. Después de 3 años de estudio y habiendo descartado a 14 candidatos, este algoritmo, también conocido como Rijndael por Vincent Rijmen y Joan Daemen, fue elegido como ganador. Aún no es un estándar, pero es de amplia aceptación a nivel mundial.

En nuestra demo utilizaremos el algoritmo AES. .NET provee implementaciones de AES, DES y TripleDES entre otros.

El algoritmo más seguro hoy el AES, aunque 3DES también es muy seguro. Este último se utiliza cuando hay necesidad de compatibilidad. AES 128 es aproximadamente 15% más rápido que DES, y AES 256 sigue siendo más rápido que DES.

Cualquiera de estos algoritmos utiliza los siguientes dos elementos; ninguno de los dos debe pasarse por alto ni subestimar su importancia:

IV (Vector de inicialización)

Esta cadena se utiliza para empezar cada proceso de encriptación. Un error común es utilizar la misma cadena de inicialización en todas las encriptaciones. En ese caso, el resultado de las encriptaciones es similar, pudiendo ahorrarle mucho trabajo a un hacker en el desciframiento de los datos. Tiene 16 bytes de largo. Puedes encontrar más información acerca de IV en http://www.atrevido.net/blog/PermaLink.aspx?guid=6136ce81-9fa1-4995-bb3e-15bc5f1f5899 .

Key (llave)

Esta es la principal información para encriptar y desencriptar en los algoritmos simétricos. Toda la seguridad del sistema depende de dónde esté esta llave, cómo esté compuesta y quién tiene acceso. El largo de las llaves depende del algoritmo. Al final del documento se darán algunas recomendaciones para el almacenamiento, generación y rotación de llaves.

Algoritmo Largos válidos (bits) Largo por defecto (bits)
DES 64 64 (8 bytes)
TripleDES 128, 192 192 (24 bytes)
Rijndael 128, 192, 256 256 (32 bytes)

Veamos ahora nuestra aplicación y cómo funciona para encriptación y desencriptación con algoritmos simétricos, específicamente Rijndael (Ver Figura 3):

Bb972216.art216-img03-482x392(es-es,MSDN.10).jpg
Figura 3: Resultado de la encriptación con Rijndael aplicado sobre una imagen y texto.Volver al texto.

Lo primero que debemos hacer es especificar una llave de encriptación. Una cadena de texto se utiliza en el ejemplo. En la caja de más abajo se puede ingresar el texto que se desea encriptar. Luego de presionar "Encriptar" se obtiene el resultado de la encriptación en la caja del medio, y como es de esperarse, presionando "DesEncriptar" se obtiene el texto desencriptado en la última caja de texto.

Si revisamos el código fuente que realiza la encriptación y desencriptación, encontraremos algo de mayor complejidad con respecto las transformaciones anteriormente vistas.

publicclass MiRijndael
{
       public static byte[] Encriptar(string strEncriptar, byte[] bytPK)
       {
              Rijndael miRijndael = Rijndael.Create();
              byte[] encrypted = null;
              byte[] returnValue = null;
 
              try
              {
                     miRijndael.Key =  bytPK;
                     miRijndael.GenerateIV();
 
                     byte[] toEncrypt =  System.Text.Encoding.UTF8.GetBytes(strEncriptar);
                     encrypted = (miRijndael.CreateEncryptor()).TransformFinalBlock(toEncrypt, 0,
 toEncrypt.Length);
 
                     returnValue = new byte[miRijndael.IV.Length + encrypted.Length];
                     miRijndael.IV.CopyTo(returnValue, 0);
                     encrypted.CopyTo(returnValue, miRijndael.IV.Length);
              }
              catch { }
              finally { miRijndael.Clear(); }
 
              return returnValue;
       }
 
       public static string Desencriptar(byte[] bytDesEncriptar, byte[] bytPK)
       {
              Rijndael miRijndael = Rijndael.Create();
              byte[] tempArray = new byte[miRijndael.IV.Length];
              byte[] encrypted = new byte[bytDesEncriptar.Length - miRijndael.IV.Length];
              string returnValue = string.Empty;
 
              try
              {
                     miRijndael.Key =  bytPK;
 
                     Array.Copy(bytDesEncriptar, tempArray, tempArray.Length);
                     Array.Copy(bytDesEncriptar, tempArray.Length, encrypted, 0, encrypted.Length);
                     miRijndael.IV = tempArray;
 
                     returnValue =  System.Text.Encoding.UTF8.GetString((miRijndael.CreateDecryptor
()).TransformFinalBlock(encrypted, 0, encrypted.Length));
              }
              catch { }
              finally { miRijndael.Clear(); }
 
              return returnValue;
       }
 
       public static byte[] Encriptar(string strEncriptar, string strPK)
       {
              return Encriptar(strEncriptar, (new PasswordDeriveBytes(strPK, null)).GetBytes(32));
       }
 
       public static string Desencriptar(byte[] bytDesEncriptar, string strPK)
       {
              return Desencriptar(bytDesEncriptar, (new PasswordDeriveBytes(strPK, null)).GetBytes(32));
       }
}

Explicar el funcionamiento del algoritmo no entra dentro del alcance de este documento y, siendo sincero, desconozco cómo funciona. Tampoco creo que sea necesario que nosotros sepamos cómo funcionan los algoritmos de encriptación. Lo importante es utilizarlos bien.

Nota: Para obtener la llave del algoritmo, dado que una cadena de carácteres generada por un humano en un formulario es considerada poco variada debido a que los carácteres utilizados son pocos (a->z, A->Z, 0->9), se le aplica un proceso de variación con la clase PasswordDeriveBytes y se le pide un resultado de 32 bytes. Esto nos retorna una llave con carácteres variados, ideal para la encriptación. PasswordDeriveBytes utiliza Hash para generar la salida. Hash se verá más adelante.

Volviendo al ejemplo, si se presiona "Encriptar" nuevamente, las veces que se desee, se verá que el resultado de la encriptación varía. ¿Por qué sucede esto, si el resultado de la encriptación debiera ser el mismo? La variación en el resultado de una encriptación se produce por dos variantes en el proceso, el Key (o llave de encriptación) y el IV (o vector de inicialización). En este caso se está utilizando el mismo Key (explícito en la caja de texto) pero el IV está variando todas las veces. ¿Porqué varía el IV? ¿Qué es el IV?

El IV o vector de inicialización es un concepto no entendido del todo, por que algunos creen que se puede encriptar “sin él”. El IV cumple un rol pequeño, pero muy importante. El único motivo por el cual existe es para apoyar el proceso de encriptación, permitiendo exactamente que ocurra lo que se observó en la caja de texto, es decir,  la variación del resultado del proceso de encriptación. El IV no debe considerarse como una segunda llave, por que no lo es. Es una ayuda y por lo mismo, no es un dato que haya que esconder, si no que basta con almacenarlo para que cuando se vaya a desencriptar se utilice el mismo IV que se utilizó en la encriptación. En el ejemplo, el IV se retorna como parte del resultado de la encriptación y se genera cada vez que se realiza un encriptación. Su largo es de 16 bytes para el algoritmo de Rijndael.

Nota: Utilizar siempre un mismo IV para no tener que almacenarlo es equivalente en seguridad a no utilizar encriptación. La seguridad mal implementada es más insegura que no implementarla. ¿Por que? Porque da la creencia de tener seguridad, y produce una confianza que es nociva. Si no tienes seguridad en tu aplicación, eso lo sabes y estás preocupado, pero si se tiene una seguridad mal implementada se cree que se tiene, pero no es así.

Con la implementación entregada en la demo, el IV está correctamente utilizado. En las imágenes de la segunda parte se aplica el mismo algoritmo de encriptación, pero utilizando variaciones en el modo de ciframiento. En la segunda imagen se aplica el modo ECB y en la tercera CBC. Estos modos especifican al algoritmo cómo debe funcionar. El modo más seguro, que también es el modo por defecto es CBC.

El modo de ciframiento ECB no utiliza IV, por lo mismo no debe usarse como modo de ciframiento, a menos que sea necesario por compatibilidad. Esta es otra muestra de porqué es útil el IV y porqué debe usarse, y que sucede con la encriptación cuando el IV no varía nunca.

Es importante considerar que en los algoritmos simétricos se utiliza la misma llave para encriptar y desencriptar. En los algoritmos asimétricos se verá que existen dos llaves, una pública para encriptar y una privada para desencriptar.

Conclusión

A través de esta primera parte del documento, se han mostrado malas prácticas y algoritmos que por creencia popular se cree que encriptan, pero que en verdad sólo transforman la información. También se revisaron los algoritmos simétricos y como se deben utilizar correctamente.

En la segunda y última parte de este documento, se explicarán los algoritmos asimétricos y el Hash.

Patrick Mac Kay es Ingeniero Civil en Computación de la Universidad de Chile desde 2000. Trabaja como Arquitecto de Software de un sitio de comercio electrónico B2B en Chile; a su cargo estuvo la implementación de este sitio en ASP .net y Visual Basic .net 2003 sobre Windows .net Web Server 2003. Posee 5 años de experiencia en el desarrollo de aplicaciones Web. Es MVP y cuenta con las certificaciones MCP en VB5, VB6 y ASP .net en Visual Basic.

Mostrar: