Cómo encriptar y desencriptar cadenas usando TwoFish, con SHA1 para el hush que genera la llave y Base64 para la codificación de la cadena

Introducción a la programación de criptografía fuerte usando Borland Delphi

Copyright © 2001 Uri Fridman <urifrid@hushmail.com>
¡Bájese el código fuente de este artículo!

Storage Library - Guarde la configuración de su aplicación

Algunas palabras

Históricamente, la criptografía fue usada por los gobiernos a través de los tiempos para ocultar mensages secretos. La necesidad de mantener "secretos de estado" fuera del alcance del público en general llevó a la invención de varios métodos de encripción.

En el Siglo XX estos métodos fueron llevados un paso más allá a medida que fueron automatizados por la introducción de máquinas como la Enigma (descifrada por los aliados en la 2da Guerra Mundial).

Con la introducción de la computadora, este proceso automatizado se hizo aún más fácil e incluso se inventaron nuevos métodos criptográficos.

Hoy las computadoras están en todos lados.

Los gobiernos tienen acceso a casi todo. Los Administradores de Sistemas tienen acceso a todos los archivos de su disco duro. La competencia tiene acceso a laptops robadas... Quiero decir, tratando de no sonar "demasiado" paranoico, que en el mundo de hoy la privacidad se hace cada vez más difícil de lograr.

Pero no se preocupen. Tenenos la Criptografía para ayudarnos.

Garantía

SIN GARANTÍA.

ESTE ARTICULO VIENE SIN NINGUNA GARANTIA, Y SE PROVEE SOBRE LA BASE "TAL Y COMO ESTA". USTED ASUME TODO EL COSTO DE CUALQUIER DAÑO RESULTANTE DE LA INFORMACION CONTENIDA EN O PRODUCIDA POR LOS CONTENIDOS DE ESTE ARTICULO. USTED ASUME TODAS LAS RESPONSABILIDADES POR LA SELECCION DE ESTE ARTICULO PARA LOGRAR SUS RESULTADOS ESPERADOS, Y POR LA INSTALACION DE, USO DE Y RESULTADOS OBTENIDOS DE EL. HASTA EL MAXIMO ALCANCE PERMITIDO POR LA LEY APLICABLE, URI, SUS PROVEEDORES Y OTROS QUE PUEDEN DISTRIBUIR ESTE ARTICULO DESCONOCEN TODA GARANTIA, YA SEA EXPRESA O IMPLICITA, INCLUYENDO PERO SIN LIMITARSE A LAS GARANTIAS IMPLICITAS DE COMERCIALIZACION, ADECUACION A UN PROPOSITO PARTICULAR, CONFORMACION CON LA DESCRIPCION, Y NO INFRINGIMIENTO, CON RESPECTO A ESTE PRODUCTO SOFTWARE.

EN OTRAS PALABRAS: USELO BAJO SU PROPIO RIESGO

Definiciones

En la terminología cripto, un archivo legible sin más se llama "Plaintext" (texto simple). El proceso de mezclar sus contenidos para hacerlo ilegible se llama Encripción, entonces estamos Encriptando un archivo. Este mensaje ilegible se llama ahora "Ciphertext" (texto cifrado). La operación inversa, desenmarañar el texto cifrado, se llama Desencripción, entonces Desencriptamos un archivo.

Estos dos procesos usan una Llave ("Key") pata encriptar o desencriptar un archivo. Sólo conociendo la llave correcta deberíamos poder desencriptar un mensaje encriptado con esa llave.

Advertencia

Estas definiciones son todas buenas y lindas, pero el mundo real es un poco diferente. Puede parecer fácil y entretenido crear "ciphers" (algortimos criptográficos) pero no lo es.

La buena encripción se ve igual que la mala. Ambas producen basura ilegible. Pero no se deje engañar. Siempre use ciphers públicos y bien conocidos, uno que haya sido estudiado por profesionales y haya sido analizado por años. Siempre trate de obtener el código fuente de los ciphers. Siga el siguiente enlace para aprender cómo diferenciar un buen producto de uno malo:

http://www.interhack.net/people/cmcurtin/snake-oil-faq.html

He tratado de ser tan preciso como sea posible al escribir este artículo. Sin embargo el español no es mi lengua natal y no soy un criptógrafo profesional, así que algunos errores pueden aparecer en el artículo. Por favor siéntase libre de comentarme acerca de ellos o de otros temas enviando un email a <urifrid@hushmail.com>.

El algoritmo

Elegí Twofish como el algoritmo para esta introducción. Lo hice por varias razones:

  1. Fue escrito por Bruce Schneier (www.counterpane.com), un criptógrafo y criptoanalista muy conocido

  2. No ha sido roto

  3. Es fácil de usar

  4. ES PUBLICO. El código fuente puede obtenerse de varios lugares.

  5. Puede uasrse en cualquier modo: ECB, CBC, CFB 8bit, OFB, OFB counter 8bit. Para conocer más acerca de los modos lea Applied Crytography (Criptografía Aplicada) de Bruce Schneier o vaya a su sitio web: http://www.counterpane.com

En breve, Twofish es:

  • Un cipher de bloques de 128 bits. Encripta bloques de 128 bits de tamaño.

  • Llaves de 128, 192 o 256 bits. Puede usar varias longitudes para las llaves. En general, si un cipher tiene una llave de 128 bits se requerirían 2^128 intentos para tratar todas y cada una de las llaves.

  • Realiza 16 rondas de encripción. Itera el cipher 16 veces antes de devolver el texto cifrado.

El código en Delphi para este algoritmo fue portado de la implementación original en C por David Barton (davebarton@bigfoot.com). Puede descargar muchos otros ciphers de su página web:

http://www.scramdisk.clara.net

Aquí proveo todos los archivos que se usan en este artículo. Los encontrará en el archivo que acompaña este artículo.

El código

Cree un nuevo proyecto. Agregue 3 cuadros de texto (Edit1, Edit2 y Edit3), 4 buttons (encrypt, decrypt, hash and browse), también agregue un radiogroup y llámelo RadioHex, inserte dos radiobuttons en él, uno "Mostrar en Hexadecimal" y el otro "Mostrar en Base64".

Vamos a agregar lo siguiente a la cláusula uses:

uses SHA1, Twofish, Base64;

  • SHA1 es el Hash. Las funciones de hash de una sola vía son como huellas digitales de los datos "hasheados" que toman una entrada de longitud variable y producen una salida de longitud fija (160 bits en SHA1). Las funciones de hash aseguran que si la información se cambia de alguna forma –aunque sea por un solo bit– se produce un valor de salida completamente diferente. Esto es útil de muchas formas. Una de ellas es por ejemplo para encriptar un archivo y luego hacer un hash del archivo encriptado. Puede enviar tanto el archivo como su hash. Cuando el otro lado recibe el archivo puede verificar si el mismo ha sido alterando creando su hash y comparándolo con el hash que ha sido enviado. Por supuesto que el hash debe ser enviado de un modo que sea seguro que no sea tocado (probablemente usando algún algoritmo público de encriptación de llaves como RSA). Vamos a usar hashes para crear una huella digital tanto de la constraseña ingreada como del archivo encriptado.

  • TWOFISH es el algortimo de encripción.

  • Base64 es una forma de codificar la salida para que sólo esté formada por caracteres imprimibles.

Agregue los siguientes procedimientos a su programa:

// produce un hash de una cadena
procedure _HashString(s: string; var Digest: TSHA1Digest);
var
  Context: TSHA1Context;               // registro para guardar los
                                       // datos intermedios
begin
  SHA1Init(Context);                   // inicializa el reg. de datos
  SHA1Update(Context,@S[1],Length(S)); // actualiza el registro de
                                       // datos con la cadena
  SHA1Final(Context,Digest);           // produce el hash final
end;

// produce un hash de un archivo
procedure _HashFile(filename: string; var Digest: TSHA1Digest);
var
  Context: TSHA1Context;               // registro para guardar los
                                       // datos intermedios
  Source: file;                        // archivo origen
  Buffer: array[1..8192] of byte;      // búfer de lectura
  Read: integer;                       // cantidad de bytes leídos
begin
  AssignFile(Source,filename);
  try
    Reset(Source,1);
  except
    MessageDlg('No se pudo abrir el archivo de origen',
      mtInformation,[mbOK],0);
    Exit;
  end;
  SHA1Init(Context);                    // inicializa el reg. de datos
  repeat
    BlockRead(Source,Buffer,Sizeof(Buffer),Read);
    SHA1Update(Context,@Buffer,Read);    // actualiza el hash
  until Read<> Sizeof(Buffer);
  SHA1Final(Context,Digest);             // produce el hash final
  CloseFile(Source);
end;

Estos procedimientos producen un hash de una sola vía para archivos y cadenas.

Note que el proceso de encripción y desencripción será como este:

  1. obtener un hash de la contraseña

  2. inicializar la llave en el algoritmo usando el hash

  3. hacer un IV (el primer bloque a usar en los modos encadenados)

  4. encriptar o desencriptar

  5. quemar datos (llave en memoria, IV, etc.) y preparar el algoritmo para la siguiete tarea.

  6. (Opcional) obtener un hash del archivo encriptado

En (1) generamos el hash de la contraseña del usuario. Este hash será usado en (2) para inicializar la llave. Esta llave NO es la contraseña que el usuario escribió. En (3) generamos el primer bloque de datos encriptados, este bloque contiene datos aleatorios. Esto se hace porque estamos usando uno de los modos encadenados donde necesitamos que cada bloque dependa del anterior para ser encriptado. Esto ayuda a ocultar patrones en el texto como redundancias y otras cabeceras de archivo (como "PK" en archivos zipeados). En (4) realizamos la verdadera encripción/desencripción y en (5) nos deshacemos de los datos ubicados en memoria (no queremos ningún cracker robando nuestra contraseña ¿verdad?).

Entonces, en el procedimiento OnClick del botón que llamamos Encrypt puede escribir lo siguiente (Advertencia, esto SOBRESCRIBIRA el archivo original. A menos que recuerde la constraseña, no podrá recuperar los datos):

var
  KeyData: TTwofishData;         // los datos de la llave inicializada
  Digest: TSHA1Digest;
  IV: array[0..15] of byte;      // el vetor de inicialización vector
                                 // necesario para modos encadenados
  Buffer: array[0..8191] of byte;
  Source,source2: file;
  i, j, n: integer;
  Key: string;
  NumRead, NumWritten: Integer;

begin
  Key:= edit2.Text; // you wrote the password in edit2
    _HashString(Key,Digest);
  TwofishInit(KeyData,@Digest,Sizeof(Digest),nil);   // inicializa los
                       // datos de la llave usando el hash de la llave
  FillChar(IV,Sizeof(IV),0);           // llenar al IV con ceros
  TwofishEncryptCBC(KeyData,@IV,@IV);  // encriptar el IV para obtener
                                       // un IV 'aleatorio'
  Move(IV,KeyData.InitBlock,Sizeof(KeyData.InitBlock));    // mover el
                             // IV en keydata para usar encadenamiento
  TwofishReset(KeyData);     // resetear keydata para usar el nuevo IV
  AssignFile(Source, edit1.Text);      // archivo a encriptar en edit1
  try
    Reset(Source,1);
  except
    TwofishBurn(KeyData);   // deshacernos de los datos
    MessageDlg('No se pudo abrir el archivo...', mtInformation,
      [mbOK], 0);
    Edit2.text:='00000000000000000000000000000000';
    Edit2.text:='';
    Exit;
  end;

  repeat
    n:= FilePos(Source);
    BlockRead(Source,Buffer,Sizeof(Buffer),i);
    for j:= 1 to (i div 16) do         // 16 es el tamaño de bloque de
                            // Twofish que procesa en bloques de bytes
    TwofishEncryptCBC(KeyData,@Buffer[(j-1)*Sizeof(IV)],  // encriptar
                      @Buffer[(j-1)*Sizeof(IV)]);

    if (i mod 16)<> 0 then        // encrypt the last bytes that don't
                                  // fit in to a full block
    begin
      Move(KeyData.LastBlock,IV,Sizeof(IV));
      TwofishEncryptCBC(KeyData,@IV,@IV);       // encriptar el bloque
              // completo nuevamente (p/que esté encriptado dos veces)
      for j:= 1 to (i mod 16) do
        // xor this encrypted block with the short block
        Buffer[(i and not 15)+j]:= Buffer[(i and not 15)+j] xor IV[j];
      end;
    Seek(Source,n);
    BlockWrite(Source,Buffer,i);   // escribir el búfer en el archivo
  until i<> Sizeof(Buffer);

  CloseFile(Source);
  TwofishBurn(KeyData); // deshacernos de los datos
  Edit2.text:='00000000000000000000000000000000';
  Edit2.text:='';
  Edit1.text:='';
  MessageDlg('Listo. El archivo ha sido encriptado con el mismo ' +
             'nombre que el original.', mtInformation, [mbOK], 0);
end;

Presto! Acabamos de encriptar el archivo en Edit1 con la contraseña en Edit2!

Para desencriptar hacemos exactamente lo mismo, pero en vez de usar TwofishEncryptCBC() usamos:

  TwofishDecryptCBC(KeyData,@Buffer[(j-1)*Sizeof(IV)],
                    @Buffer[(j-1)*Sizeof(IV)]);

Simple, ¿no?

Muy bien, tiene su archivo encriptado. Un lindo toque sería generar el hash de ese archivo encriptado y enviarlo junto con él.

Para lograr esto usaremos el procedimiento _HashFile que escribimos antes. En el procedimiento OnClick para el botón que llamó Hash agregue lo siguiente:

procedure TForm1.HashClick(Sender: TObject);
var
  Digest: TSHA1Digest;   // Forma binaria del hash
  i: integer;
begin
if Edit1.Text='' then exit;      // asegurarnos que tenemos un archivo
  _HashFile(Edit1.Text,Digest);  // calcular el hash y
                                 // guardarlo en Digest
if radiohex.ItemIndex=0 then     // queremos mostrar el
                                 // hash en hexadecimal
begin
  Edit3.Text:= '0x';
  for i:= 0 to (Sizeof(Digest)-1) do
    Edit3.Text:= Edit3.Text+IntToHex(Digest[i],2); // convertir Digest
                                           // a una cadena hexadecimal
end
else // in base64
 begin
  Edit3.Text:= '';
  for i:= 0 to (Sizeof(Digest)-1) do
    Edit3.Text:= Edit3.Text+chr(Digest[i]); // convertir Digest a
                                            // base64
  Edit3.Text:=B64Encode(Edit3.Text);
 end;
end;

Ahora podemos elegir guardar el hash en un archivo y comprimir tanto el archivo encriptado como su hash en un solo arcihvo .zip. Podemos ahora enviar el archivo zip a un amigo estando seguros que sólo el/ella podrá desencriptarlo.

Esta es una simple aplicación. Mucho más puede agregársele para hacerla más segura, pero este es sólo un ejemplo. Le queda a usted agregar su propio código y recuerde:

"...que los fuentes lo acompañen."

El código fuente de SHA1, Twofish y Base64 junto con el programa que los usa puede encontrarlo en el archivo que acompaña este artículo. Por favor asegúrese de leer la garantía muy cuidadosamente.

Gracias.

Uri, Febrero 10, 2001. - urifrid@hushmail.com


NOTA: Por si tiene interés, Uri tiene una aplicación freeware que presenta una interfaz tipo explorador que le permite encriptar y desencriptar archivos. También hay disponible una versión de consola:

http://www.geocities.com/urifrid/soft.html

JfControls Library - para Delphi y C++ Builder