Cifrado y firmado de archivos con GPG desde linea de comando

Generar un nuevo par de llaves

$ gpg --gen-key

gpg (GnuPG) 1.4.18; Copyright (C) 2014 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:

(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)

Your selection? 1

RSA keys may be between 1024 and 4096 bits long.

What keysize do you want? (2048) 2048

Requested keysize is 2048 bits

Please specify how long the key should be valid.

0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years

Key is valid for? (0) 0

Key does not expire at all

Is this correct? (y/N) y

You need a user ID to identify your key; the software constructs the user ID from the Real Name, Comment and Email Address in this form "Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>"

Real name: Jorge I. Meza
Email address: jimezam @autonoma.edu.co
Comment: PGP certificate for work

You selected this USER-ID: "Jorge I. Meza (PGP certificate for work) <jimezam @autonoma.edu.co>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O

You need a Passphrase to protect your secret key.

We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy.

Not enough random bytes available. Please do some other work to give the OS a chance to collect more entropy! (Need 239 more bytes)

gpg: key 525E50C5 marked as ultimately trusted public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u pub 2048R/525E50C5 2015-10-15
Key fingerprint = 3792 F014 361E 93E7 355F 7FCA C153 2279 525E 50C5
uid Jorge I. Meza (PGP certificate for work) <jimezam @autonoma.edu.co> sub 2048R/E56672A6 2015-10-15

Listar las llaves conocidas

Las llaves públicas

$ gpg --list-keys

/home/educacion/.gnupg/pubring.gpg
----------------------------------
pub 2048R/525E50C5 2015-10-15
uid Jorge I. Meza (PGP certificate for work) <jimezam @autonoma.edu.co>
sub 2048R/E56672A6 2015-10-15

Las llaves privadas

$ gpg --list-secret-keys

/home/educacion/.gnupg/secring.gpg
----------------------------------
sec 2048R/525E50C5 2015-10-15
uid Jorge I. Meza (PGP certificate for work) <jimezam @autonoma.edu.co>
ssb 2048R/E56672A6 2015-10-15

Exportar mi llave pública

$ gpg --armor --export -a 525E50C5 > public.key

Importar la llave pública de otro usuario

$ gpg --import public.key

Cifrar un archivo

$ gpg --recipient 525E50C5 --armor --encrypt ARCHIVO.EXT

En este caso se utilizará la llave pública identificada por el código 525E50C5 para cifrar el archivo.  Este proceso genera ARCHIVO.EXT.asc el cual corresponde a la versión cifrada de ARCHIVO.EXT y es el archivo que se podrá transmitir a través de un medio inseguro.

Descifrar un archivo

$ gpg --output ARCHIVO.EXT --decrypt ARCHIVO.EXT.asc

Firmar un archivo

$ gpg --detach-sign ARCHIVO.EXT

Se genera el archivo ARCHIVO.EXT.sig el cual corresponde a la firma del archivo ARCHIVO.EXT y se deberá transmitir junto con el archivo original.

Verificar la firma de un archivo

Se deberá contar con el archivo de la firma (.sig) y con el archivo original, así como con la llave pública de su supuesto autor.

$ gpg ARCHIVO.EXT.sig

En caso de que el documento corresponda con el expedido por el autor original se obtendrá un mensaje similar al siguiente.

gpg: assuming signed data in `ARCHIVO.EXT'
gpg: Signature made jue 15 oct 2015 12:40:56 COT using RSA key ID 525E50C5
gpg: Good signature from "Jorge I. Meza (PGP certificate for work) <jimezam @autonoma.edu.co>"

En caso de que el documento NO corresponda con el expedido por el autor original se obtendrá un mensaje similar al siguiente.

gpg: assuming signed data in `ARCHIVO.EXT'
gpg: Signature made jue 15 oct 2015 12:40:56 COT using RSA key ID 525E50C5
gpg: BAD signature from "Jorge I. Meza (PGP certificate for work) <jimezam @autonoma.edu.co>"

Recursos

  1. The GNU Privacy Guard
    https://gnupg.org/
  2. GNU Privacy Guard en Wikipedia
    https://en.wikipedia.org/wiki/GNU_Privacy_Guard
  3. Curso sencillo de PGP por Arturo Quirantes Sierra
    http://www.ugr.es/~aquiran/cripto/cursopgp.htm
  4. GPG Tutorial
    https://futureboy.us/pgp.html
  5. GnuPrivacyGuardHowto en Ubuntu Documentation
    https://help.ubuntu.com/community/GnuPrivacyGuardHowto
  6. PGP Tutorial
    http://aperiodic.net/phil/pgp/tutorial.html

Montar una partición cifrada en Ubuntu

Introducción

Las versiones recientes de los sistemas operativos como Ubuntu permiten que se cifren las particiones (usualmente la que contiene a /home) para evitar así que terceros tengan acceso a la información contenida en estas si se obtiene el acceso físico a las mismas.

Esta mejora en la seguridad algunas veces se convierte en problema cuando por razones válidas es necesario acceder a esta partición desde otro sistema diferente del original.  Tal es el caso cuando se desactiva o pierde la contraseña del usuario administrador.

En ese caso es necesario iniciar el sistema con un livecd de Ubuntu (o montarlo físicamente en otro equipo), montar la partición que contenga al sistema de archivos raíz y realizar las modificaciones necesarias en los archivos de configuración para garantizar el futuro acceso a la cuenta de usuario en cuestión.  Estos pasos se ven modificados cuando la partición a montarse se encuentra cifrada.

Montar la partición cifrada

Identificar cual es la partición que se desea montar.

$ sudo fdisk -l

   Device Boot      Start         End      Blocks   Id  System
   /dev/sda1              63       96389       48163+  de  Dell Utility
   /dev/sda2   *       98304   163938303    81920000    7  HPFS/NTFS/exFAT
   /dev/sda3       163938304   167843839     1952768   82  Linux swap / Solaris
   /dev/sda4       167845886   234440703    33297409    5  Extended
   /dev/sda5       167845888   189327359    10740736   83  Linux
   /dev/sda6       189329408   234440703    22555648   83  Linux

En este ejemplo se determina que la partición que se desea montar temporalmente es /dev/sda5 que contiene al sistema de archivos raíz.

Instalar los paquetes requeridos si estos no se encuentran aún en el sistema.

$ sudo apt-get install lvm2 cryptsetup

Agregar el módulo del kernel necesario para acceder a los dispositivos de bloque cifrados.

$ sudo modprobe dm-crypt

Configurar al módulo de cifrado de Linux para que reconozca a la partición que se va a montar.

$ sudo cryptsetup luksOpen /dev/sda5 crypt1

En este punto el sistema solicita al usuario la contraseña secreta (passphrase) que se utilizó para el cifrado de la partición.

Identificar los grupos de volúmenes cifrados disponibles.  El nombre obtenido aquí debe tenerse en cuenta para un paso posterior.  En el caso de la partición cifrada por defecto se encontró que el nombre del grupo era ubuntu.

$ sudo vgscan --mknodes

Crear un directorio que servirá como punto de montaje para la partición cifrada.

$ mkdir /montaje

Montar la partición cifrada en el punto de montaje recién creado.

$ sudo mount /dev/ubuntu/root /montaje

A partir de este momento el sistema de archivos cifrado puede accederse normalmente bajo el punto de montaje establecido.  Por ejemplo para acceder gráficamente a sus archivos es necesario utilizar las herramientas del sistema.

$ sudo nautilus /montaje

Desbloquear un usuario

Una vez se pueda acceder al sistema de archivos raíz de la máquina afectada es posible realizar tareas de gestión de los usuarios como desbloquear cuentas o cambiar sus contraseñas.

Primero es necesario tomar temporalmente a la partición montada como partición raíz del sistema para que sean tenidos en cuenta sus usuarios durante las tareas de administración y no los del livecd, para hacer esto es necesario ejecutar el siguiente comando.

$ sudo chroot /montaje

Desbloquear las cuentas de usuario requeridas.

$ sudo passwd -u usuario

O simplemente modificar sus contraseñas.

$ sudo passwd usuario

Una vez terminadas las modificaciones que se iban a realizar sobre la partición cifrada se debe reiniciar el sistema normalmente.

Cifrado y descifrado asimétrico con RSA utilizando C#/Mono

Introducción.

En criptografía, RSA (Rivest, Shamir y Adleman) es un sistema criptográfico de clave pública desarrollado en 1977. En la actualidad, RSA es el primer y más utilizado algoritmo de este tipo y es válido tanto para cifrar como para firmar digitalmente.

La seguridad de este algoritmo radica en el problema de la factorización de números enteros. Los mensajes enviados se representan mediante números, y el funcionamiento se basa en el producto, conocido, de dos números primos grandes elegidos al azar y mantenidos en secreto. Actualmente estos primos son del orden de 10200, y se prevé que su tamaño aumente con el aumento de la capacidad de cálculo de los ordenadores.

Como en todo sistema de clave pública, cada usuario posee dos claves de cifrado: una pública y otra privada. Cuando se quiere enviar un mensaje, el emisor busca la clave pública del receptor, cifra su mensaje con esa clave, y una vez que el mensaje cifrado llega al receptor, este se ocupa de descifrarlo usando su clave privada.

Se cree que RSA será seguro mientras no se conozcan formas rápidas de descomponer un número grande en producto de primos. La computación cuántica podría proveer de una solución a este problema de factorización.

Tomado del artículo RSA de Wikipedia.

Implementación.

La aplicación de demostración de esta técnica requiere del uso de por lo menos los siguientes namespaces.

using System;
using System.IO;
using System.Security;
using System.Security.Cryptography;

Proveedor del servicio de cifrado RSA.

El núcleo de la implementación es el objeto RSACryptoServiceProvider que realiza las labores de cifrado/descifrado de información.

public static RSACryptoServiceProvider rsa;

Establecer los valores iniciales del servicio.

En este punto se establecen los valores iniciales de configuración del servicio, los cuales son en su mayoría opcionales, y se instancia el objeto mencionado anteriormente.

const string CONTAINER_NAME = "ContenedorRSA";

CspParameters cspParams;
cspParams = new CspParameters(1);	// PROV_RSA_FULL
cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
cspParams.KeyContainerName = CONTAINER_NAME;

// Instanciar el algoritmo de cifrado RSA

rsa = new RSACryptoServiceProvider(cspParams);

Generar las llaves.

En esta etapa se crea una pareja de llaves: pública y privada, para las labores de cifrado.  Estas se almacenan por conveniencia en formato XML en los archivos llave_publica.xml y llave_privada.xml correspondientemente.

// Generar y almacenar la llave pública

writer = new StreamWriter(@"llave_publica.xml");
string publicOnlyKeyXML = rsa.ToXmlString(false);
writer.Write(publicOnlyKeyXML);
writer.Close();

// Generar y almacenar la llave privada

writer = new StreamWriter(@"llave_privada.xml");
string publicPrivateKeyXML = rsa.ToXmlString(true);
writer.Write(publicPrivateKeyXML);
writer.Close();

Cargar las llaves.

Una vez que han sido creadas y almacenadas las llaves, estas no deben ser creadas nuevamente.  Para su uso se cargan desde los correspondientes archivos XML y se asocian con el proveedor de cifrado.

// Cargar y asociar la llave pública al proveedor de cifrado

StreamReader reader = new StreamReader(@"llave_publica.xml");
string publicOnlyKeyXML = reader.ReadToEnd();
rsa.FromXmlString(publicOnlyKeyXML);
reader.Close();

// Cargar y asociar la llave privada (y pública) al proveedor de cifrado

StreamReader reader = new StreamReader(@"llave_privada.xml");
string publicPrivateKeyXML = reader.ReadToEnd();
rsa.FromXmlString(publicPrivateKeyXML);
reader.Close();

Cifrar la información.

Para realizar el cifrado de los datos es necesario contar con la llave pública del destinatario.

cargarLlavePublica();

// Convertir el texto a cifrar (plano) a su representación en bytse

byte[] textoPlanoBytes = System.Text.Encoding.UTF8.GetBytes(textoPlano);

// Realizar el proceso de cifrado

byte[] textoCifradoBytes = rsa.Encrypt(textoPlanoBytes, false);

// Convertir el mensaje cifrado a su representación en cadena

string MensajeCifrado = Convert.ToBase64String(textoCifradoBytes);

Descifrar la información.

Para descifrar los datos y obtener el mensaje original es necesario contar con la llave privada del destinatario del mensaje.

cargarLlavePrivada();

// Convertir el texto cifrado a su representación en bytse

byte[] textoCifradoBytes = Convert.FromBase64String(textoCifrado);

// Realizar el proceso de descifrado

byte[] textoPlanoBytes = rsa.Decrypt(textoCifradoBytes, false);

// Convertir el mensaje descifrado a su representación en cadena

string MensajeDescifrado = System.Text.Encoding.UTF8.GetString(textoPlanoBytes);

Aplicación de demostración.

Aplicación de demostración del uso del algoritmo
Aplicación de demostración del uso del algoritmo RSA

$ gmcs “/out:RSASample.exe” “/r:/usr/lib/mono/2.0/System.dll” /t:exe “RSASample/Main.cs”

Enlaces.

Cifrado y descifrado simétrico con Rijndael (AES) utilizando C#/Mono

Introducción.

Advanced Encryption Standard (AES), también conocido como Rijndael (pronunciado “Rain Doll” en inglés), es un esquema de cifrado por bloques adoptado como un estándar de cifrado por el gobierno de los Estados Unidos. El AES fue anunciado por el Instituto Nacional de Estándares y Tecnología (NIST) como FIPS PUB 197 de los Estados Unidos (FIPS 197) el 26 de noviembre de 2001 después de un proceso de estandarización que duró 5 años.  Se transformó en un estándar efectivo el 26 de mayo de 2002. Desde 2006, el AES es uno de los algoritmos más populares usados en criptografía simétrica.

El cifrador fue desarrollado por dos criptólogos belgas, Joan Daemen y Vincent Rijmen, ambos estudiantes de la Katholieke Universiteit Leuven, y enviado al proceso de selección AES bajo el nombre “Rijndael”.

Tomado del artículo Advanced Encryption Standard de Wikipedia.

Implementación con strings.

La aplicación de demostración de esta técnica requiere del uso de por lo menos los siguientes namespaces.

using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;

Establecer la clave y el vector de inicio.

Estos valores pueden ser especificados manualmente o de manera automática por el framework.  La implementación para permitir que se definan automáticamente estos valores es la siguiente.

Rijndael rijndael = Rijndael.Create();
byte[] key = rijndael.Key;
byte[] iv  = rijndael.IV;

Es posible forzar la generación de nuevas claves y nuevos vectores de inicio para el algoritmo utilizando los métodos rijndael.GenerateKey() y rijndael.GenerateIV() respectivamente.

Si por el contrario se desea especificar estos valores manualmente su implementación es la siguiente siendo strKey y strIv, la clave y el vector de inicialización como cadenas de texto.

byte[] key = UTF8Encoding.UTF8.GetBytes(strKey);
byte[] iv  = UTF8Encoding.UTF8.GetBytes(strIv);

Especificando estos valores manualmente es necesario garantizar que sus longitudes sean válidas para el algoritmo.  En este caso se utilizará una longitud de clave de 32 bits y una longitud de vector de inicio de 16 bits.

int keySize = 32;
int ivSize = 16;

Array.Resize(ref key, keySize);
Array.Resize(ref iv, ivSize);

Cifrado de cadenas de texto.

Para cifrar la información se requiere de los siguientes parámetros.

  1. Cadena de texto con los datos a cifrar.
  2. Clave.
  3. Vector de inicio.

El proceso retornará finalmente una cadena de texto con los datos cifrados.

/**
 * Cifra una cadena texto con el algoritmo de Rijndael
 *
 * @param	plainMessage	mensaje plano (sin cifrar)
 * @param	Key		        clave del cifrado para Rijndael
 * @param	IV		        vector de inicio para Rijndael
 * @return	string		        texto cifrado
 */

public static string encryptString(String plainMessage, byte[] Key, byte[] IV)
{
    // Crear una instancia del algoritmo de Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Establecer un flujo en memoria para el cifrado

    MemoryStream memoryStream = new MemoryStream();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 RijndaelAlg.CreateEncryptor(Key, IV),
                                                 CryptoStreamMode.Write);

    // Obtener la representación en bytes de la información a cifrar

    byte[] plainMessageBytes = UTF8Encoding.UTF8.GetBytes(plainMessage);

    // Cifrar los datos enviándolos al flujo de cifrado

    cryptoStream.Write(plainMessageBytes, 0, plainMessageBytes.Length);

    cryptoStream.FlushFinalBlock();

    // Obtener los datos datos cifrados como un arreglo de bytes

    byte[] cipherMessageBytes = memoryStream.ToArray();

    // Cerrar los flujos utilizados

    memoryStream.Close();
    cryptoStream.Close();

    // Retornar la representación de texto de los datos cifrados

    return Convert.ToBase64String(cipherMessageBytes);
}

Descifrado de cadenas de texto.

El proceso inverso, el de descifrado, se realiza de manera antagónica.  Para hacerlo es necesario contar con los siguientes parámetros.

  1. Cadena de texto con los datos cifrados.
  2. Clave.
  3. Vector de inicio.

El proceso retornará finalmente una cadena de texto con los datos descifrados.

/**
 * Descifra una cadena texto con el algoritmo de Rijndael
 *
 * @param	encryptedMessage	mensaje cifrado
 * @param	Key			clave del cifrado para Rijndael
 * @param	IV			vector de inicio para Rijndael
 * @return	string			texto descifrado (plano)
 */

public static string decryptString(String encryptedMessage, byte[] Key, byte[] IV)
{
    // Obtener la representación en bytes del texto cifrado

    byte[] cipherTextBytes = Convert.FromBase64String(encryptedMessage);

    // Crear un arreglo de bytes para almacenar los datos descifrados

    byte[] plainTextBytes = new byte[cipherTextBytes.Length];

    // Crear una instancia del algoritmo de Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo en memoria con la representación de bytes de la información cifrada

    MemoryStream memoryStream = new MemoryStream(cipherTextBytes);

    // Crear un flujo de descifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 RijndaelAlg.CreateDecryptor(Key, IV),
                                                 CryptoStreamMode.Read);

    // Obtener los datos descifrados obteniéndolos del flujo de descifrado

    int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);

    // Cerrar los flujos utilizados

    memoryStream.Close();
    cryptoStream.Close();

    // Retornar la representación de texto de los datos descifrados

    return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}

Implementación con archivos.

El cifrado y descifrado de mensajes en archivos se realiza de manera similar al expuesto anteriormente con cadenas, sin embargo varían los flujos (streams) utilizados para obtener y dirigir el flujo de la información.

Cifrado a archivos.

/**
 * Cifra una cadena texto con el algoritmo de Rijndael y lo almacena en un archivo
 *
 * @param	plainMessage	mensaje plano (sin cifrar)
 * @param	filename	        nombre del archivo donde se almacenará el mensaje cifrado
 * @param	Key		        clave del cifrado para Rijndael
 * @param	IV		        vector de inicio para Rijndael
 * @return	void
 */

public static void encryptToFile(String plainMessage, String filename, byte[] Key, byte[] IV)
{
    // Crear un flujo para el archivo a generarse

    FileStream fileStream = File.Open(filename, FileMode.OpenOrCreate);

    // Crear una instancia del algoritmo Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(fileStream,
                                                 RijndaelAlg.CreateEncryptor(Key, IV),
                                                 CryptoStreamMode.Write);

    // Crear un flujo de escritura basado en el flujo de cifrado

    StreamWriter streamWriter = new StreamWriter(cryptoStream);

    // Cifrar el mensaje a través del flujo de escritura

    streamWriter.WriteLine(plainMessage);

    // Cerrar los flujos utilizados

    streamWriter.Close();
    cryptoStream.Close();
    fileStream.Close();
}

Descifrado de archivos.

/**
 * Descifra el contenido de un archivo con el algoritmo de Rijndael y lo retorna
 * como una cadena de texto plano
 *
 * @param	filename		nombre del archivo donde se encuentra el mensaje cifrado
 * @param	Key			clave del cifrado para Rijndael
 * @param	IV			vector de inicio para Rijndael
 * @return	string			mensaje descifrado (plano)
 */

public static string decryptFromFile(String filename, byte[] Key, byte[] IV)
{
    // Crear un flujo para el archivo a generarse

    FileStream fileStream = File.Open(filename, FileMode.OpenOrCreate);

    // Crear una instancia del algoritmo Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(fileStream,
                                                 RijndaelAlg.CreateDecryptor(Key, IV),
                                                 CryptoStreamMode.Read);

    // Crear un flujo de lectura basado en el flujo de cifrado

    StreamReader streamReader = new StreamReader(cryptoStream);

    // Descifrar el mensaje a través del flujo de lectura

    string plainMessage = streamReader.ReadLine();

    // Cerrar los flujos utilizados

    streamReader.Close();
    cryptoStream.Close();
    fileStream.Close();

    return plainMessage;
}

Aplicación de demostración.

La aplicación de demostración incluye los conceptos y el código expuestos en este artículo.  Con ella es posible cifrar y descifrar un mensaje que consiste en una cadena de texto arbitraria en memoria y en un archivo.

Aplicación de demostración del uso del algoritmo Rijndael
Aplicación de demostración del uso del algoritmo Rijndael

Construír la aplicación.

La aplicación de demostración puede construírse utilizando la solución incluída en la distribución con MonoDevelop o Visual Studio.  También es posible construírla desde línea de comando (Mono) mediante la siguiente instrucción.

$ gmcs “/out:RijndaelSample.exe” “/r:/usr/lib/mono/2.0/System.dll” /t:exe “RijndaelSample/Main.cs”

Enlaces.