9.1.3.6.2. Cifrado de bloques

Mediante esta técnica el software hace de intermediario entre los bloques físicos y los bloques de un dispositivo virtual cifrando en las escrituras y descifrando en las lecturas.

../../../../_images/dm-crypt.png

En consecuencia:

  • Ciframos un dispositivo de bloques entero.

  • Como el cifrado es independiente del sistema de archivos, se puede utilizar cualquier sistema de archivos.

  • Permite no sólo el cifrado de datos, sino el cifrado del sistema operativo de arranque, preparando convenientemente el sistema (caso que no abordaremos aquí, pero que puede consultarse, por ejemplo, en un artículo de la wiki de Archlinux).

Utilizaremos LUKS y abordaremos el caso más sencillo de querer cifrar una partición física, para lo cual debemos primero instalar el software de cifrado:

# apt install cryptsetup

Operativa manual

Lo primero es mapear una partición física[1] (p.e. /dev/sda6) sobre un dispositivo virtual:

# cryptsetup -y -v luksFormat /dev/sda6  # Requerirá una contraseña
# cryptsetup open /dev/sda6 cifrado      # Debemos proporcionar la contraseña

Esto generará el dispositivo virtual de bloques /dev/mapper/cifrado, sobre el cual podemos actuar como si se tratara de un dispositivo físico, o sea:

# mkfs.ext4 -L DATOSECRETOS /dev/mapper/cifrado
# mount /dev/mapper/cifrado /mnt

Si en algún momento quisiéramos desmontar todo:

# umount /mnt
# cryptsetup close cifrado

Operativa automatizada

Que el administrador deba llevar a cabo estas operaciones cada vez que se arranca el sistema, no es algo operativo. Para semiautomatizar el montaje durante el arranque podemos añadir la asociación entre el dispositivo físico y el virtual en /etc/crypttab:

# echo "cifrado /dev/sda6 none" >> /etc/crypttab

y la asociación entre el dispositivo virtual y el punto de montaje en /etc/fstab:

# echo "/dev/mapper/cifrado /mnt ext4 defaults 0 0" >> /etc/fstab

El montaje será semiautomático, porque durante el proceso de arranque deberemos digitalizar la contraseña. Es posible, también, en vez de que la clave sea interactiva, guardarla en un archivo. Es más, LUKS dispone de ocho slots para almacenar claves alternativas. Ahora mismo sólo habría una:

# cryptsetup luksDump /dev/sda6
LUKS header information
Version:        2
Epoch:          8
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           e26d3cf8-20a7-422f-ac8f-83340e63725f
Label:          (no label)
Subsystem:      (no subsystem)
Flags:          (no flags)

Data segments:
  0: crypt
        offset: 16777216 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 512 [bytes]

Keyslots:
  0: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      argon2i
        Time cost:  4
        Memory:     98948
        Threads:    1
        Salt:       a0 a1 57 4c 30 6a af e5 de 76 d5 d8 a9 f0 11 b7
                    ac b5 c6 90 d0 1d 4e 92 4d 1c 4b b5 4c 07 97 70
        AF stripes: 4000
        AF hash:    sha256
        Area offset:32768 [bytes]
        Area length:58048 [bytes]
        Digest ID:  0

Tokens:
Digests:
  0: pbkdf2
        Hash:       sha256
        Iterations: 39337
        Salt:       2b c9 51 10 c7 29 4b 63 35 a4 83 63 bc 36 46 2f
                    49 92 af dd 32 a8 7c 9d 19 08 51 80 1b 58 6f 56
        Digest:     0c 52 b0 1d 8c 80 2e 6b 45 0a c8 ac 4a b2 e9 a2
                    f4 bf 81 e6 5a 00 c4 42 af 10 21 9c 3a 92 fe 6c

con lo que podemos añadir al mismo sistema otra clave que esté en un archivo. Para ello, vamos primero a generar esa clave, constituida por 512 bytes totalmente aleatorios:

# dd < /dev/urandom > /root/luks.key bs=512 count=1

que, podemos consultar en formato hexadecimal, así:

# od -v -An -tx1 /root/luks.key  # Consultamos la clave
dc 12 ae d8 2c b5 4e 12 56 a9 35 b4 5f a6 29 b9
[...]

Con la clave ya en el archivo /root/luks.key, podemos añadirla a un slot:

# cryptsetup luksAddKey /dev/sda6 /root/luks.key
# cryptsetup luksDump /dev/sda6
[...]
Keyslots:
  0: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      argon2i
        Time cost:  4
        Memory:     98948
        Threads:    1
        Salt:       a0 a1 57 4c 30 6a af e5 de 76 d5 d8 a9 f0 11 b7
                    ac b5 c6 90 d0 1d 4e 92 4d 1c 4b b5 4c 07 97 70
        AF stripes: 4000
        AF hash:    sha256
        Area offset:32768 [bytes]
        Area length:258048 [bytes]
        Digest ID:  0
  1: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      argon2i
        Time cost:  4
        Memory:     100952
        Threads:    1
        Salt:       b1 63 a9 24 aa cc f5 9c b4 6c 8a 8b 27 7a cb 2c
                    72 cd f8 d9 68 b9 1b f4 43 c7 d6 b5 20 81 47 c5
        AF stripes: 4000
        AF hash:    sha256
        Area offset:290816 [bytes]
        Area length:258048 [bytes]
        Digest ID:  0
[...]

Por último, si en /etc/crypttab modificamos la línea para que se use el archivo:

cifrado     /dev/sda6      /root/luks.key

durante el arranque no se pedirá ninguna clave y el sistema se encontrará montado al acabar la secuencia.

Advertencia

Ahora bien, ¿para qué ciframos una partición si dejamos la clave para su descifrado en un archivo de otra partición sin cifrar?

Lo interesante de lo anterior es, simplemente, comprobar que se puede guardar la clave en un archivo y usarlo para no tener que escribirla interactivamente. Y ello es útil, si almacenamos el archivo en un dispositivo externo como un pincho USB que procuremos retirar y llevarnos lejos de la máquina cuando no la usemos. Además, es conveniente ocultar ese archivo para que pase desapercibido si alguien se hace con nuestro pincho. A este respecto, lo más juicioso es guardar los 512 bytes de la clave en algún espacio libre del pincho USB y ajeno a los sistemas de archivos que pueda haber en él:

  • Si el particionado es DOS, podemos utilizar los últimos 512 bytes del espacio entre el MBR y la primera partición, ya que al principio de ese espacio puede haber código de un gestor de arranque como GRUB.

  • Si el particionado es GPT, podemos utilizar los últimos 512 bytes del espacio que se reserva para definir particiones, ya que es bastante improbable que en el pincho hayamos creado más de 124 particiones.

Pongamos este segundo caso de ejemplo. En un disco GPT:

  • El primer sector es un MBR ficticio (512B)

  • El segundo sector es la cabecera GPT (512B)

  • A continuación hay espacio para 128 definiciones de particiones cada una de las cuales ocupa 128 bytes (16KiB).

En consecuencia el comienzo del disco ocupa 17KiB o lo que es lo mismo 34 sectores, así que podemos ocupar el sector 34 para almacenar nuestra clave, con el único costo de que "sólo" podremos definir 124 particiones, lo cual, ciertamente, no parece ningún problema.

Supongamos que el pincho se encuentra en /dev/sdb[2]:

# gdisk -l /dev/sdb
[...]
Number  Start (sector)    End (sector)  Size       Code  Name
   1             416          103003   50.1 MiB    EF00  EFI System Partition
   2          103008        30719966   14.6 GiB    0700  Microsoft basic data

Vamos a crear una clave aleatoria de 512 bytes directamente sobre su sector 34:

# dd < /dev/urandom > /dev/sdb bs=512 count=1 seek=33

y, creada, la añadimos a un slot:

# { echo "secreto" ; dd < /dev/sdb bs=512 count=1 skip=33; } | cryptsetup luksAddKey /dev/sda6 -

donde «secreto» es la contraseña que introdujimos al crear el dispositivo cifrado y que nos servía para hacer el montaje interactivo. Añadida esta clave, podemos probar si funciona del siguiente modo:

# dd < /dev/sdb bs=512 count=1 skip=33 | cryptsetup open /dev/sda6 cifrado --key-file=-

que debe generar el dispositivo virtual y, si continua la línea en /etc/fstab, montarnos directamente la partición sobre /srv. Ya tenemos la mitad del trabajo hecho, ya que aún falta que al arrancar el sistema busque el dispositivo, lo monte y lleve a cabo justamente esta operación.

Para ello, debemos crear una regla para udev, que al detectar el dispositivo USB lance un script:

# cat > /etc/udev/rules.d/70-usb.rules
SUBSYSTEMS=="usb", ACTION=="add", ATTRS{idVendor}=="abcd", ATTRS{idProduct}=="1234", \
   KERNEL=="sd?", SYMLINK+="usbkey", RUN+="/usr/local/bin/unlock.sh"

La regla identifica el dispositivo en el que hemos guardado la clave a través de su idVendor e idProduct que se pueden consultar fácilmente al hacer:

$ lsusb
[...]
Bus 002 Device 002: ID abcd:1234 Unknown
[...]

Además, aprovechamos la regla para añadir un enlace simbólico /dev/usbkey que apunte al dispositivo. Con este nombre podremos referirnos al dispositivo dentro del script:

#!/bin/sh
RT="/dev/sda6"
DEVICE="/dev/usbkey"
ENCVOL="cifrado"
MOUNTP="/srv"

{
   until [ -b "$PART" ]; do sleep .5; done
   dd < "$DEVICE" bs=512 count=1 skip=33 | \
      cryptsetup open "$PART" "$ENCVOL" --key-file=-
} &

Por último, en /etc/crypttab no debe existir referencia alguna, ya que es el script el que realiza la operación de crear el dispositivo cifrado. En /etc/fstab, sí podemos dejar la línea, pero añadiendo la opción nofail, para que no falle el montaje y pare el arranque en caso de que no se encuentre el pincho:

/dev/mapper/cifrado /srv   ext4   defaults,nofail  0 0

Nota

Esta estrategia está tomada de esta entrada de /dev/blog y sólo es válida si se cifra una partición de datos y no la partición del sistema. Si se lleva a cabo el cifrado del sistema, es necesario recurrir a otra estrategia totalmente distinta basada en manipular la imagen initramfs.

Notas al pie