Encriptación Hash (Contraseñas)

Éste es un algoritmo de los que se conocen como de sólo ida, ya que no es posible desencriptar lo que se ha encriptado. Puede ser que a primera vista no se le vea la utilidad, pero en los siguientes dos escenarios, éste es el algoritmo necesario para realizar el proceso. En éstos, el primero es proteger información muy valiosa encriptando el contenido y el segundo es validar que no se modifique la información que se está enviando desde algún lugar a otro. Veamos el primer escenario.


Almacenar contraseñas de un sistema. Las contraseñas de cualquier sistema serio jamás debieran poder desencriptarse. El motivo es simple. Si se pueden desencriptar, el riesgo que alguien conozca la llave y tenga acceso a todo el sistema con todos los usuarios es muy grande. Entonces, si no puedo desencriptar una contraseña, ¿Cómo puedo saber entonces si una contraseña entregada es válida si no puedo compararla? La respuesta es muy simple. Se encripta la contraseña entregada y se compara el resultado de la encriptación con la contraseña previamente almacenada.


Problemas: Si bien el problema no está tan relacionado con el Hash, debido a la utilización de contraseñas de escasa dificultad, haciendo un ataque de fuerza bruta o por diccionario se podrían encontrar valores de Hash que coinciden con el Hash de las contraseñas almacenadas. Para esto se pueden ejecutar dos planes de acción independientes, pero obteniéndose el mejor resultado con la utilización de ambos.


La solución 1 consiste en realizar varias pasadas de Hash sobre los datos, aplicándole el Hash al resultado del Hash anterior. Realizando esto varios cientos de veces (idealmente mil veces) podemos dificultar más el trabajo de un hacker.


La solución 2 requiere agregar un texto adicional a la contraseña. Esto se conoce como Salt. Entonces, cada vez que un usuario cambie o esté validando una contraseña, hay que concatenarle a la contraseña este Salt y de ahí generar el Hash. De esta forma le agrega variación a las contraseñas pobremente definidas. Esta solución requiere un trabajo adicional ya que es necesario además almacenar el texto del Salt junto a la información del usuario y la contraseña encriptada. Este texto se debe crear con caracteres generados al azar.


En el ejemplo se mostrará la combinación de ambas soluciones. Como encriptación de Hash se usará SHA1. Los otros algoritmos mencionados con anterioridad están disponibles también en .NET.


public static byte[] Encriptar(string strPassword, string strSalt, int intIteraciones)

{


      string _strPasswordSalt = strPassword + strSalt;


      SHA1 _objSha1 = SHA1.Create();


      byte[] _objTemporal = null;


      try


      {


              _objTemporal = System.Text.Encoding.UTF8.GetBytes(_strPasswordSalt);


              for (int i = 0; i <= intIteraciones-1; i++)


                     _objTemporal = _objSha1.ComputeHash(_objTemporal);


       }


       finally


       {


              _objSha1.Clear();


       }


       return _objTemporal;


}


El resultado de este Hash (Sha1) siempre retorna 160 bits, es decir, 20 bytes. Cuidado con confundir con el largo del string resultante de la conversión a Base64. Esta conversión genera más caracteres. Para verificar, pueden medir el largo del arreglo colocando un punto de interrupción en el retorno (return), y notarán que son 20 bytes.


Además, las funciones de hash no tienen limitante para el tamaño del texto de entrada. Sea cual sea el tamaño, el largo de la salida es el mismo y se procesan todos los caracteres del string de entrada.


Para un sistema de almacenamiento de contraseñas de usuario seguro, hay que hacer algunas modificaciones a esta función, pero son mínimas. Las modificaciones no pasan por cambios en el algoritmo, sino mas bien por crear una función para que genere Salt randómicos y crear la función para la comparación de las contraseñas.


La función para comparar arreglos de Bytes es la siguiente


private static bool CompareByteArray(Byte[] arrayA, Byte[] arrayB)

{


       if (arrayA.Length != arrayB.Length) return false;


       for (int i = 0 ; i <= arrayA.Length – 1; i++)


             if (!arrayA[i].Equals(arrayB[i])) return false;


       return true;


}


Patrick Mac Kay
Noviembre 2004.

Encriptación Asimétrica

La encriptación asimétrica nos permite que dos personas puedan enviarse información encriptada, sin necesidad de compartir la llave de encriptación. Se utiliza una llave pública para encriptar el texto y una llave privada para desencriptarlo. A pesar de que puede sonar extraño que se encripte con un pública y desencripte con la privada, el motivo para hacerlo es el siguiente. Si alguien necesita que le envíen la información encriptada, él deja disponible la llave pública para que quienes le desean enviar algo lo encripten. Nadie puede desencriptar algo con la misma llave pública. El único que puede desencriptar es quien posea la llave privada, quien justamente es el que recibe la información encriptada.


Los algoritmos de encriptación asimétrica mas conocidos son:



  • RSA (Rivest, Shamir, Adleman): Creado en 1978, hoy es el algoritmo de mayor uso en encriptación asimétrica. Tiene dificultades en encriptar grandes volúmenes de información, por lo que es usado por lo general en conjunto con algoritmos simétricos.

  • Diffie-Hellman (& Merkle): No es precisamente un algoritmo de encriptación sino un algoritmo para generar llaves públicas y privadas en ambientes inseguros.

  • ECC (Elliptical Curve Cryptography): Es un algoritmo que se utiliza poco, pero tiene importancia cuando es necesario encriptar grandes volúmenes de información.

Como era de esperarse, en nuestra demostración utilizaremos el algoritmo de mayor uso, RSA. Debido a que el funcionamiento es diferente comparado con el algoritmo anterior, la pantalla varia levemente. Tendremos una zona donde se desplegará la llave pública generada, dejándole el almacenamiento de la llave privada a la clase miRSA.


La idea de esto es que exista una clase que te entregue una llave publica para que tu encriptes la información y luego le envíes ésta a la misma clase para que con la llave privada la desencripte.


El codigo en el lado del que va a desencriptar (a quien le mandan el texto encriptado) queda así



public class miRSA


{


       private RSACryptoServiceProvider _objKey = null;


 


       public miRSA()


       {


             this._objKey = new RSACryptoServiceProvider(1024);


       }


 


       public string ObtenerLlavePublica()


       {


             return this._objKey.ToXmlString(false);


       }


 


       private string DesEncriptar(byte[] bytEncriptado)


       {


             return System.Text.Encoding.UTF8.GetString(this._objKey.Decrypt(bytEncriptado, false));


       }


 


}


Y el código del cliente público es



private miRSA _objKey  = new miRSA();


 


RSACryptoServiceProvider _objEncriptadorPublico = new RSACryptoServiceProvider();


 


_objEncriptadorPublico.FromXmlString(this._objKey.ObtenerLlavePublica());


_objEncriptadorPublico.Encrypt(System.Text.Encoding.UTF8.GetBytes(this.txtAsimAEncriptar.Text), false);


En este ejemplo, la encriptación se produce en un lugar, luego de solicitar la clave pública a la clase (pero que perfectamente puede ser un servicio web), la información encriptada se puede enviar a este servicio web sin problemas de violación de seguridad, y sin necesidad de conocer ambas llaves. Si bien es cierto que la clase o servicio conoce ambas llaves, la llave pública no le es de ninguna utilidad.


Patrick Mac Kay
Noviembre 2004.

Encriptación Simétrica

La Encriptación simétrica se utiliza cuando necesito almacenar información crítica, que deberá poder descifrarse, y seré yo el único que haga todo el proceso. Nadie mas tendrá acceso a la llave con que se encriptará y desencriptará la información.


El proceso de realizar una encriptación es complejo para ser entendido por nosotros mismos, pero no es limitante para conocer cuales son los pasos para utilizarlos y que 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 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 DES, pero al ser nuevo, aun no es aceptado como un estándar, aunque no se le han encontrado debilidades aún.
  • AES (Advanced Encryption Standard): Éste 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. Aun no es un estándar, pero es de amplia aceptación a nivel mundial. Junto a 3DES es de los más seguros.

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): Ésta 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.
  • Key (llave): Esta es la principal información para encriptar y desencriptar en los algoritmos simétricos. Toda la seguridad del sistema depende de donde este esta llave, como esté compuesta y quien tiene acceso. El largo de las llaves depende del algoritmo.

Bueno, despues de una introducción tan larga, vámos al código, donde utilizamos AES o Rijndael.



public class MiRijndael


{


       public static byte[] Encriptar(string strEncriptar, string strPK)


       {


             System.Text.UTF8Encoding textConverter = new UTF8Encoding();


             Rijndael miRijndael = Rijndael.Create();


             byte[] encrypted = null;


             byte[] returnValue = null;


 


             try


             {


                    miRijndael.Key = textConverter.GetBytes(strPK);


 


                    byte[] toEncrypt = textConverter.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 {      }


             return returnValue;


       }


 


       public static string Desencriptar(byte[] bytDesEncriptar, string strPK)


       {


             System.Text.UTF8Encoding textConverter = new UTF8Encoding();


             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 = textConverter.GetBytes(strPK);


 


                    Array.Copy(bytDesEncriptar, tempArray, tempArray.Length);


                    Array.Copy(bytDesEncriptar, tempArray.Length, encrypted, 0, encrypted.Length);


                    miRijndael.IV = tempArray;


 


                    returnValue = textConverter.GetString((miRijndael.CreateDecryptor()).TransformFinalBlock(encrypted, 0, encrypted.Length));


 


             }


             catch { }


             return returnValue;


       }


}


El resultado de la encriptación es un arreglo de Bytes. Si se quiere un string se puede ejecutar el método GetString(arreglo de bytes) de cualquier converter (UTF8 como en el ejemplo), como tambien transformarlo con Convert.ToBase64String(arreglo de bytes).


Patrick Mac Kay
Noviembre 2004.

Enviar archivos al navegador del cliente

Para mandar el contenido de un archivo al browser o navegador del cliente, y presentarle el cuado de diálogo si desea abrir, guardar o cancelar este archivo, se debe cumplir lo siguiente:


Construir una página sin código html. Esto es muy importante ya que cuando se envíe el contenido del archivo, no debe haberse enviado código html ya que la mezcla de estos (el código y el archivo) hará que el navegador del usuario no entienda bien el contenido.


En el código fuente de esta página, agregar las siguientes líneas (los números son sólo de referencia)


1.- Response.ClearHeaders();


2.- Response.ClearContent();


3.- Response.AddHeader(“Content-Disposition”, “attachment;filename=NOMBRE_DEL_ARCHIVO“);


4.- Response.ContentType = “application/pdf“;


5.- Response.BinaryWrite(ARREGLO_DE_BYTES_CON_EL_CONTENIDO_DEL_ARCHIVO);


6.- Response.Flush();


7.- Response.Close();


En este caso, las líneas 1 y 2 limpian el buffer que se haya llenado con información que no corresponde al archivo. Esto es necesario si en algún momento antes de haber llegado a estas líneas, se hizo algún response.write.


Las líneas 3 y 4 le dicen al navegador del cliente las instrucciones correspondientes al envío de un archivo llamado NOMBRE_DEL_ARCHIVO y del tipo que es. En este caso, se le especifica que es un pdf. Más abajo se desplegarán los tipos más comunes, y un link a una página muy completa con los tipos existentes.


La línea 5 escribe el arreglo de bytes del archivo en el cliente. Posteriormente entregaré una función para sacar un arreglo del bytes de un archivo.


Por último, las líneas 6 y 7 vacían el buffer y cierran el envío al cliente.


Un listado con algunos de los tipos MIME más comunes:


























Texto Plano


text/plain


Imagen gif / jpg


image/gif o image/jpeg


Video Mpeg


video/mpeg


Postscript


application/postscript


Pdf


application/pdf


MS Powerpoint


application/mspowerpoint


MS Excel


application/vnd.ms-excel o application/ms-excel


La URL con muchos de los tipos MIME disponibles es:
http://www.utoronto.ca/webdocs/HTMLdocs/Book/Book-3ed/appb/mimetype.html


Patrick
Publicado, Noviembre 2004.
Revisado, Enero 2007.