|
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!
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:
Fue escrito por Bruce Schneier (www.counterpane.com), un criptógrafo
y criptoanalista muy conocido
No ha sido roto
Es fácil de usar
ES PUBLICO. El código fuente puede obtenerse de varios lugares.
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:
obtener un hash de la contraseña
inicializar la llave en el algoritmo usando el hash
hacer un IV (el primer bloque a usar en los modos encadenados)
encriptar o desencriptar
quemar datos (llave en memoria, IV, etc.) y preparar el algoritmo
para la siguiete tarea.
(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
|