Integridad de archivos en sistemas GNU/Linux y Unix

En este artículo voy a explicar cómo podemos saber si un fichero ha sido alterado, o comprobar si se trata del mismo archivo que esperamos que sea. Casi todos los sistemas operativos GNU ya vienen de serie con software para manejar algoritmos de reducción criptográfica como MD5. Podremos debatir sobre si se trata del mejor algoritmo o no, pero no es el objetivo de este post. Así pues, aprovechemos la ocasión ya que probablemente no tengamos que instalar nada en nuestra máquina.

Para este ejemplo, lo primero que haremos es crear un archivo de texto plano con una frase cualquiera.

cat archivo.txt
Este archivo es de prueba.

Ahora ejecutamos el comando md5sum al que le pasaremos como argumento el nombre del archivo. Esto nos devolverá un valor alfanumérico único.

md5sum archivo.txt
ae2e5a6e2d2067069248e928cc8dddb1  archivo.txt

Si estuviéramos en un sistema Unix como Solaris tendríamos que ejecutar el comando digest con la opcion -a para indicar el tipo de algoritmo, en este caso MD5:

digest -a md5 archivo.txt
ae2e5a6e2d2067069248e928cc8dddb1

Como podemos observar, la cadena resultante es la misma en un sistema GNU/Linux que en Solaris. Bien, ahora vamos a modificar el contenido del archivo y veamos que pasa. Vamos a cambiar el punto del final por una exclamación.

cat archivo.txt
Este archivo es de prueba!

Repetimos el proceso anterior para ver su cadena md5 y... voilà.

Linux

md5sum archivo.txt
398d5ec7a57375dce2657ff0fd8fd53e  archivo.txt

Solaris

digest -a md5 archivo.txt
398d5ec7a57375dce2657ff0fd8fd53e

La cadena alfanumérica MD5 ha cambiado.

- "¿Y para que me sirve todo esto?"

Supongamos que tienes un archivo que quieres compartir y lo subes a internet para que la gente lo descargue. Esta cadena alfanumérica sería algo así como el DNI de ese archivo, la matrícula, el certificado que verifica que el archivo original es este y solamente este. Cualquier modificación en el interior del archivo, sea cual sea, un punto, una coma, un espacio, hará que cambie esta cadena MD5.

Aunque con esto ya sería suficiente para comprobar que un archivo ha sido manipulado de algún modo, es posible pasar esta cadena resultante por otro algoritmo de cifrado como base64, por ejemplo con openssl. Esta sería el método en cualquier sistema operativo GNU/Linux y Unix:

Linux

md5sum archivo.txt | awk '{print $1}' | openssl enc -base64
Mzk4ZDVlYzdhNTczNzVkY2UyNjU3ZmYwZmQ4ZmQ1M2UK

Solaris

digest -a md5 archivo.txt | openssl enc -base64
Mzk4ZDVlYzdhNTczNzVkY2UyNjU3ZmYwZmQ4ZmQ1M2UK

Notese que en el caso de GNU/Linux he tenido que meter un awk '{print $1}' para quedarme solo con la parte de la cadena MD5, cosa que en Solaris no es necesario.

Encriptar un archivo con clave GPG

A continuación explico de forma muy breve cómo se encripta un archivo cualquiera con una clave GPG. *Debes tener instalado en tu sistema GnuPG.

Para este ejemplo primero crearemos un archivo prueba.txt que contendrá la cadena de texto "Hola".

echo "Hola" > prueba.txt
cat prueba.txt 
Hola

Ahora ejecutamos el siguiente comando sobre el archivo prueba.txt.

gpg --passphrase abcd1234 -o prueba.gpg -c prueba.txt

Al argumento --passphrase se le puede pasar la cadena de texto que queramos. Eso si, debemos recordarla para luego desencriptar nuestro archivo. El argumento -o es para indicar el archivo de salida ya encriptado, y el argumento -c es para indicar que se va a realizar un cifrado simétrico (por defecto AES128). Si se quisiera cambiar el tipo de cifrado se puede sustituir la opción -c por --cipher-algo y a continuación especificar el tipo de cifrado, por ejemplo:

gpg --passphrase abcd1234 -o prueba.gpg --cipher-algo AES256 prueba.txt

Una vez hecho esto, se puede listar el contenido del directorio actual para ver lo que se ha generado.

ls -lrt
-rw-rw-r-- 1 xe26505 xe26505    5 nov 03 19:43 prueba.txt
-rw-rw-r-- 1 xe26505 xe26505   54 nov 03 19:43 prueba.gpg

Si queremos ver qué contiene el archivo prueba.gpg generado veremos que está encriptado:

cat prueba.gpg 
??K0pF`?%<??Z?8??>??Tgh???_u???O?
????8a?

Ahora ya podemos guardar para nosotros mismos o hacer llegar el archivo a una persona que conozca la passphrase para descifrarlo, de un modo seguro y fiable.

Para desencriptar el archivo bastaría con ejecutar el siguiente comando:

gpg --passphrase abcd1234 -d prueba.gpg 
gpg: CAST5 encrypted data
gpg: encrypted with 1 passphrase
Hola

Donde al argumento --passphrase se le ha de pasar la misma cadena que se utilizó para encriptar, y el argumento -d es el archivo encriptado.

Si lo que se quiere encriptar es un conjunto de archivos y directorios bastaría con empaquetarlos y/o comprimirlos en un archivo, por ejemplo .tar, .zip o .gz y repetir el proceso de este mini tutorial.

- "¿Y para qué necesito yo hacer todo esto?"

Si necesitas guardar algo de gran valor, por ejemplo una semilla de un monedero Bitcoin, un archivo de passwords, o necesitas enviar por mail datos con información sensible (informe médico, cuenta del banco, passwords, etc.) es altamente recomendable hacerlo de forma que nadie mas que tú y quien tú quieras lo pueda leer. Aquí puedes leer cómo usar GnuPG para tu correo electrónico.

Login en un programa hecho en C

Imagina que tienes un programa hecho en C que sirve para desencriptar passwords. Ahora imagina que no quieres que lo utilice nadie mas que aquellos usuarios que sepan una password. Muchos pensarán que con asignar al binario unos permisos chmod y chown adecuados sería suficiente. Nunca es suficiente. Por ello, hoy voy a explicar una manera de añadir a nuestro programa un simple sistema de autenticación que funciona bien. Este es el código de mi archivo fuente softwareConLogin.c:

/* Compilacion:	gcc -std=gnu99 -Wall -c -MMD -MP -MF"softwareConLogin.d" -MT"softwareConLogin.d" -o "softwareConLogin.o" softwareConLogin.c
 * 		gcc -o softwareConLogin softwareConLogin.o -lcrypt
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <crypt.h>
 
int main(void) {
	const char * const pass = "$1$Fw1PJ/2K$KhVip8FBuJMNXpZh.XqGX.";
	char *result;
	int ok;
 
	result = crypt(getpass("Password: "), pass);
	ok = strcmp(result, pass) == 0;
 
	puts(ok ? "Correcto!" : "Password erronea");
	return ok ? 0 : 1;
}

En este programa de ejemplo he utilizado una librería llamada crypt.h (cortesía de la FSF) por lo que tendremos que especificarlo a la hora de compilarlo con el flag -lcrypt.

Fijaos bien en la siguiente línea:

const char * const pass = "$1$Fw1PJ/2K$KhVip8FBuJMNXpZh.XqGX.";

Esa es nuestra password encriptada. ¿Podríamos ponerla en claro?, si pero cualquiera que tenga acceso al código fuente o a un buen decompilador podría ver cual es nuestra password, y eso no nos interesa. Lo que he hecho ha sido meter en el código fuente la password ya encriptada mediante un método de encriptación MD5/DES que ya expliqué otro el otro día. De este modo, al ejecutar nuestro software lo primero que nos va a pedir es una Password, y si se introduce correctamente el programa continuará, de lo contrario, dará un error de autenticación y finalizará. Se puede mejorar mucho, por ejemplo, añadiendo número de intentos, un usuario además de la password (también encriptado), etc. En unos días pondré una versión mejorada con todo esto y mas.

Encriptar cadenas con MD5/DES en C

Voy a explicar un simple ejemplo de programa en C para encriptar cadenas que se podrán usar a posteriori para securizar el uso de nuestros programas una vez compilados. Es muy sencillo y es una práctica que todos deberíamos ir adquiriendo, y en los tiempos que corren mas que nunca. Para ello he creado un archivo fuente encriptaCadena.c con el siguiente código:

/* Compilacion:	gcc -std=gnu99 -Wall -c -MMD -MP -MF"encriptaCadena.d" -MT"encriptaCadena.d" -o "encriptaCadena.o" encriptaCadena.c
 * 		gcc -o encriptaCadena encriptaCadena.o -lcrypt
 */

#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <crypt.h>
 
int main(void) {
	unsigned long seed[2];
	char salt[] = "$1$3xe26505";
	const char * const seedchars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
	char *cadena;
	int i;
 
	seed[0] = time(NULL);
	seed[1] = getpid() ^ (seed[0] >> 14 & 0x30000);
 
	for (i = 0; i < 8; i++) {
		salt[3 + i] = seedchars[(seed[i / 5] >> (i % 5) * 6) & 0x3f];
	}
 
	cadena= crypt(getpass("Cadena: "), salt);
 
	puts(cadena);
	return 0;
}

He utilizado una librería llamada crypt.h (cortesía de la FSF) por lo que tendremos que especificarlo a la hora de compilarlo con el flag -lcrypt. Una vez compilado lo podemos usar de la siguiente manera (ejemplo en un sistema GNU/Linux o Unix):

./encriptaCadena HolaQueTal
$1$Fw1PJ/2K$KhVip8FBuJMNXpZh.XqGX.

Como se puede ver, la ejecución del programa devuelve una cadena encriptada $1$Fw1PJ/2K$KhVip8FBuJMNXpZh.XqGX. que podremos utilizar para validar una password de autenticación en otro programa que pida un Login. Esto es Free Software, compártelo.