Chiffrement transparent des données utilisateurs avec LUKS/dm-crypt 🔐

Jusqu’à la version 17.10, Ubuntu proposait une option permettant de chiffrer son répertoire utilisateur. Très simple à mettre en œuvre, il suffisait de cocher une case lors d’une étape de l’installation pour que les données de l’utilisateur soient protégées, par exemple en cas de vol de l’ordinateur.

Cette solution avait l’avantage de proposer une solution de chiffrement des données transparente pour l’utilisateur, puisque le déchiffrement se faisait automatiquement lors de l’ouverture d’une session utilisateur.

« Crypter » est à « décrypter » ce que « truire » est à « détruire » : ça ne veut rien dire !
— L’auteur de ce blog
Vous voulez en savoir plus ?

Cette option n’est plus proposée à l’installation dans les versions récentes d’Ubuntu et eCryptfs est considéré comme déprécié, en faveur de l’utilisation du standard en terme de chiffrement dans le monde Linux, LUKS mis en œuvre à l’aide de dm-crypt.

La seule option proposée lors de l’installation d’Ubuntu et reposant sur LUKS/dm-crypt consiste à chiffrer totalement son système, pas uniquement le répertoire utilisateur. Ainsi, le démarrage nécessite la saisie d’une passphrase pour déchiffrer le disque pour que le système puisse démarrer (je schématise). Des données sécurisées, certes, mais une perte de confort d’utilisation.

Dans cet article nous allons voir comment installer la dernière version d’Ubuntu en date (20.04), en mettant en œuvre du chiffrement avec LUKS/dm-crypt, tout en conservant (presque) le confort d’utilisation que procurait la solution eCryptfs.

Préparatifs avant installation

Avant de procéder à l’installation, nous allons passer par la ligne de commandes pour préparer le disque avant d’utiliser le programme d’installation d’Ubuntu.

Invites de commandes

Dans les exemples de commandes shell de cet article :

  • l’invite # indique que la commande doit être exécutée avec les privilèges root, soit directement en tant qu’utilisateur root, soit en utilisant la commande sudo.

  • L’invite $ indique que la commande doit être exécutée sans privilège root.

  1. Démarrer une session live à l’aide d’une clé USB d’installation d’Ubuntu, puis ouvrir un terminal.

    la session live doit être lancée en mode UEFI.
  2. Identifier le chemin du périphérique bloc [1] correspondant au disque sur lequel dérouler l’installation, à l’aide de la commande lsblk:

    • Dans la suite de ce guide, nous utiliserons le disque /dev/sda pour illustrer les commandes à exécuter.

    • Ce guide part du principe que le disque /dev/sda est vierge de tout volume LVM. Si tel n’est pas le cas, supprimer toutes traces liées à LVM du disque /dev/sda avant de dérouler la suite du guide :

      # vgremove -y -ff <volume-group-name>(1)
      # pvremove -y -ff /dev/sda*(2)
      1 Supprime un groupe de volume.
      2 Supprime tous les volumes physiques des partitions du disque /dev/sda.
  3. Réinitialiser le partitionnement du disque /dev/sda

    # sgdisk --zap-all /dev/sda \(1)
        && partprobe(2)
    
    1 Supprime les structures de données MBR et GPT du disque /dev/sda.
    2 Informe le système d’exploitation des changements apportés aux tables de partitions.
  4. Partitionner le disque /dev/sda :

    • créer une partition système EFI :

      # sgdisk -n1:0:+512M \(1)
          -t1:EF00 \(2)
          -c1:esp \(3)
          /dev/sda
      1 Crée une nouvelle partition numérotée 1, de taille 512 Mio [2]
      2 Spécifie le type de la partition 1. Ici EF00 désigne une partition système EFI [3].
      3 Nom donné à la partition : « esp », pour EFI System Partition.
    • créer une partition destinée à être gérée par LVM :

      # sgdisk -n2:0:0 \(1)
          -t2:8E00 \(2)
          -c2:main \(3)
          /dev/sda
      1 Crée une nouvelle partition numérotée 2, occupant tout l’espace libre restant du disque.
      2 Spécifie que la partition 2 est de type LVM.
      3 Nomme la partition « main ».

      Le système d’exploitation utilise ce libellé pour définir un raccourci vers cette partition dans le système de fichiers. On pourra par la suite référencer cette partition à l’aide du chemin /dev/disk/by-partlabel/main.

    • Vérifier le partitionnement du disque /dev/sda :

      # sgdisk --print /dev/sda

      La commande doit retourner un résultat cohérent avec les options indiquées dans les précédentes commandes.

      Exemple 1. résumé du partitionnement d’un disque avec sgdisk
      # sgdisk --print /dev/sda
      ...
      Number Start (sector) End (sector)  Size      Code  Name
           1           2048      1050623  512.0 MiB EF00  esp
           2        1050623    500118158  238.0 GiB 8E00  main
  5. Formater la partition EFI :

    # mkfs.fat -F32 /dev/disk/by-partlabel/esp
  6. Initialiser la partition main en tant que volume physique pour LVM :

    # pvcreate /dev/disk/by-partlabel/main
  7. Créer un groupe de volumes LVM en y incluant la partition main :

    # vgcreate vgmain \(1)
        /dev/disk/by-partlabel/main(2)
    
    1 Nom donné au groupe de volumes : « vgmain » (🤷‍♂️).
    2 Chemin des périphériques blocs à ajouter au groupe de volumes (ici, une seule partition).
  8. Créer des volumes logiques dans vgmain :

    • créer un premier volume logique pour la racine du système de fichiers :

      # lvcreate -y --name root \(1)
          --size 50G \(2)
          vgmain
      1 Nom donné au volume logique : « root ».
      2 Taille du volume logique : 50 Gio.
    • Créer un deuxième volume logique pour un espace swap :

      # lvcreate -y --name swap \(1)
          --size 2G \(2)
          vgmain
      1 Nom donné au volume logique : « swap ».
      2 Taille du volume logique : 2 Gio.
    • Créer un dernier volume logique destiné à être chiffré :

      # lvcreate -y --name crypt \(1)
          --extents 100%FREE \(2)
          vgmain
      1 Nom donné au volume logique : « crypt ».
      2 Le volume logique occupera 100% de l’espace disponible.
  9. Chiffrer le volume logique crypt :

    # cryptsetup luksFormat \(1)
        /dev/mapper/vgmain-crypt(2)
    
    1 Aucune option spécifiée, les options par défaut suivantes sont utilisées :
    --type luks

    Utilise la spécification de chiffrement de disque LUKS. Plus précisément, c’est LUKS2 qui est mis en œuvre depuis la version 2.1.0 de cryptsetup.

    --cipher aes-xts-plain64

    Algorithme de chiffrement.

    --key-size 512

    Taille de la clé de chiffrement.

    --hash sha256

    Algorithme de hachage utilisé pour la dérivation de clé [4].

    --iter-time 2000

    Nombre d’itération de la fonction de dérivation de clé.

    --use-urandom

    Utilise la source d’entropie /dev/urandom.

    --verify-passphrase

    Demande confirmation de la phrase de passe.

    2 Chemin du périphérique bloc à chiffrer.

    La commande demande la saisie d’une passphrase pour chiffrer le périphérique bloc, ainsi que la confirmation de cette passphrase.

    L’objectif étant de déchiffrer automatiquement le répertoire utilisateur après l’authentification, le mot de passe du compte utilisateur qui sera créé pendant l’installation devra être identique à la passphrase saisie ici.

    Lors de mes tests dans une machine virtuelle avec seulement 2 Gio de mémoire, la commande cryptsetup luksFormat se terminait par un laconique « Processus arrêté » affiché dans la sortie standard. Ceci est lié à la fonction de dérivation de clé Argon2i [5] très gourmande en mémoire. Veiller donc à avoir suffisamment de mémoire sur la machine ou alors ajouter l’option --pbkdf-memory <max-memory-in-kilobytes> à la ligne de commande pour limiter la quantité de mémoire utilisée.

  10. Déverrouiller le volume chiffré :

    # cryptsetup open --type=luks \
        /dev/mapper/vgmain-crypt \(1)
        home-jl(2)
    
    1 Chemin du périphérique bloc chiffré à rendre accessible.
    2 Nom donné au périphérique bloc au travers duquel le contenu déchiffré sera accessible.

    Le périphérique bloc créé est à présent accessible avec le chemin /dev/mapper/home-jl.

  11. Formater le périphérique bloc :

    # mkfs -t ext4 /dev/mapper/home-jl
    Sans cette étape, le périphérique bloc n’est pas visible pendant l’installation d’Ubuntu.

Installation d’Ubuntu 20.04

Nous allons dérouler normalement l’installation d’Ubuntu à l’aide du programme dédié.

  1. Lancer le programme d’installation.

  2. Arrivé à l’étape de sélection du type d’installation, sélectionner Autre chose, puis cliquer sur Continuer.

    sélection du type d’installation
    Figure 1. Installation d’Ubuntu — sélection du type d’installation
  3. Dans l’outil de partitionnement, définir /dev/mapper/vgmain-root en tant que racine du système de fichiers.

    définition de la racine du système de fichiers
    Figure 2. Installation d’Ubuntu — définition de la racine du système de fichiers
  4. Sélectionner ensuite /dev/mapper/home-jl pour le répertoire utilisateur.

    Bien faire attention à saisir /home/jl en point de montage. Nous verrons plus tard comment faire en sorte de déverrouiller puis monter le volume après authentification de l’utilisateur.
    sélection du volume pour le répertoire utilisateur
    Figure 3. Installation d’Ubuntu — sélection du volume pour le répertoire utilisateur
  5. Indiquer /dev/mapper/vgmain-swap comme espace d’échange, puis cliquer sur Installer maintenant

    définition de l’espace _swap_
    Figure 4. Installation d’Ubuntu — définition de l’espace swap
  6. Confirmer les changements à appliquer en cliquant sur Continuer.

    confirmation du partitionnement
    Figure 5. Installation d’Ubuntu — confirmation du partitionnement
  7. Arrivé à l’étape « Qui êtes-vous ? », saisir les informations du compte utilisateur à créer, puis cliquer sur Continuer pour terminer l’installation.

    pour faire en sorte de déchiffrer le volume du répertoire utilisateur à la connexion de celui-ci, il est primordial de saisir un mot de passe identique à la passphrase saisie lors du chiffrement du volume.
    création du compte utilisateur
    Figure 6. Installation d’Ubuntu — création du compte utilisateur
À la fin de l’installation, ne pas redémarrer l’ordinateur. Il nous reste des choses à faire.

Avant de redémarrer !

Après l’installation il nous reste à faire en sorte que l’ouverture d’une session utilisateur entraîne le déchiffrement puis le montage de son volume personnel. Nous allons également prendre soin de chiffrer l’espace swap.

Si par erreur un redémarrage a eu lieu à la fin de l’installation, penser à déverrouiller manuellement le volume chiffré avant de dérouler les instructions ci-dessous.

  1. Configurer des points de montages pour faire un chroot dans l’environnement installé :

    # mount /dev/mapper/vgmain-root /mnt \(1)
        && mount /dev/disk/by-partlabel/esp /mnt/boot/efi \
        && mount /dev/mapper/home-jl /mnt/home/jl
    # mount --rbind /dev  /mnt/dev \(2)
        && mount --rbind /proc /mnt/proc \
        && mount --rbind /sys  /mnt/sys
    # cp /etc/resolv.conf /mnt/etc/resolv.conf(3)
    # chroot /mnt(4)
    1 Monte les différents périphériques pour reproduire le système de fichiers de la machine installée.
    2 Monte des pseudo systèmes de fichiers de l’hôte.
    3 Copie le fichier de résolution des DNS.
    4 change root
  2. Copier des fichiers de configuration manquants dans le répertoire utilisateur :

    # cp /etc/skel/.* /home/jl
  3. Créer un script de déverrouillage du volume logique chiffré /usr/local/bin/pam-crypt-open.sh

    #!/bin/sh
    
    CRYPT_USER="jl"
    MAPPER_NAME="home-$CRYPT_USER"
    MAPPER="/dev/mapper/$MAPPER_NAME"
    
    if [ "$PAM_USER" = "$CRYPT_USER" ] && [ ! -e $MAPPER ]
    then
      tr '\0' '\n' | /sbin/cryptsetup open --type=luks /dev/mapper/vgmain-crypt "$MAPPER_NAME"
    fi

    Ce script permet d’utiliser le mot de passe saisi par l’utilisateur lors de l’ouverture de session pour déchiffrer le volume /dev/mapper/vgmain-crypt (si l’identifiant utilisateur est celui attendu et si le volume n’est pas déjà ouvert).

  4. Attribuer les droits d’exécution à ce script

    # chmod u+x /usr/local/bin/pam-crypt-open.sh
  5. Configurer pam pour rendre accessible le contenu du volume chiffré lors d’une ouverture de session :

    # echo "auth    optional    pam_exec.so expose_authtok /usr/local/bin/pam-crypt-open.sh" >> /etc/pam.d/common-auth

    Le module pam_exec permet d’appeler le script ajouté ci-dessus à l’ouverture d’une session utilisateur.

  6. Faire en sorte de monter le volume chiffré uniquement lorsque nécessaire (à l’ouverture de session, après déchiffrement) :

    # sed -i -e "s/\(\/home\/jl\s\+ext4\s\+\)defaults/\1rw,suid,dev,exec,noauto,nouser,async/" /etc/fstab

    La commande ci-dessus modifie le fichier /etc/fstab et remplace le paramètre defaults de montage du répertoire utilisateur (qui équivaut à rw,suid,dev,exec,auto,nouser,async) par la valeur rw,suid,dev,exec,noauto,nouser,async.

  7. Configurer le chiffrement de l’espace swap

    • Définir la configuration de chiffrement de l’espace swap :

      # echo "swap\(1)
          /dev/mapper/vgmain-swap\(2)
          /dev/urandom\(3)
          plain,swap,cipher=aes-xts-plain64,size=256" \(4)
          > /etc/crypttab(5)
      1 L’espace swap déchiffré sera accessible au travers du périphérique bloc /dev/mapper/swap.
      2 Chemin du périphérique bloc à déchiffrer.
      3 La clé de chiffrement est générée aléatoirement à chaque démarrage
      4 Indique que le mode de chiffrement est plain, puis spécifie l’utilisation en tant qu’espace swap ainsi que les options de chiffrement.
      5 Le fichier /etc/crypttab décrit les périphériques blocs qui sont déchiffrés au démarrage.

      Ce mode de chiffrement de l’espace swap empêche son utilisation pour de l’hibernation (suspend to disk).

    • Mettre à jour le chemin de l’espace swap dans le fichier /etc/fstab :

      # sed -i -e "s/\/dev\/mapper\/vgmain-swap/\/dev\/mapper\/swap/" /etc/fstab
    • Désactivier l’utilisaton de l’espace swap pour l’hibernation :

      # echo "RESUME=none" > /etc/initramfs-tools/conf.d/resume && update-initramfs -u
  8. Quitter « l’environnement » chroot :

    # exit
  9. Démonter toute l’arborescence /mnt :

    # mount | grep /mnt | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}
  10. Nous en avons terminé dans cette session live. Nous pouvons redémarrer l’ordinateur.

Démarrage

L’installation est terminée et l’ordinateur est utilisable en l’état. Nous allons malgré tout renforcer la sécurité de cette installation en faisant en sorte de démonter le répertoire personnel, puis verrouiller le volume chiffré lorsque l’utilisateur clôt sa session.

  1. Ouvrir un terminal.

  2. Configurer le démontage de /home/jl lors de la fermeture de session

    Après fermeture d’une session utilisateur, systemd arrête automatiquement le service user@1000.service (où 1000 est le PID de l’utilisateur jl). En spécifiant que le point de montage /home/jl requiert le fonctionnement de ce service, systemd le démontera automatiquement à l’arrêt de celui-ci.

    • Éditer le fichier de configuration systemd correspondant au point de montage /home/jl (mount unit) :

      # systemctl edit home-jl.mount

      Et ajouter une dépendance vers l’unité user@1000.service :

      [Unit]
      Requires=user@1000.service

      Enregistrer puis quitter l’éditeur.

      En procédant ainsi, on introduit une dépendance circulaire qu’il faut résoudre en modifiant la configuration du service user@1000.service.

    • Éditer l’unité de service user@1000.service :

      # systemctl edit user@1000.service

      Et ajouter les contraintes suivantes :

      [Unit]
      Requires=home-jl.mount
      After=home-jl.mount
  3. Configurer le verrouillage du volume chiffré à la fermeture de session.

    Pour parvenir à cet objectif, nous allons définir un nouveau service qui démarre au déverrouillage du volume chiffré et se termine au démontage du répertoire /home/jl, tout en déclenchant le verrouillage du volume.

    • Créer le service cryptsetup-jl.service :

      # systemctl edit --full --force \(1)
          cryptsetup-jl.service(2)
      
      1 Combinaison d’options à saisir pour créer un nouveau service :
      --full

      spécifie d’éditer le fichier de configuration complet.

      --force

      force la création du fichier s’il n’existe pas.

      2 Nom donné au service.

      Avec la configuration suivante :

      [Unit]
      DefaultDependencies=no
      BindsTo=dev-mapper-vgmain\x2dcrypt.device
      After=dev-mapper-vgmain\x2dcrypt.device
      BindsTo=dev-mapper-home\x2djl.device
      Requires=home-jl.mount
      Before=home-jl.mount
      Conflicts=umount.target
      Before=umount.target
      
      [Service]
      Type=oneshot
      RemainAfterExit=yes
      TimeoutSec=0
      ExecStop=/sbin/cryptsetup close home-jl
      
      [Install]
      RequiredBy=dev-mapper-home\x2djl.device

      dev-mapper-vgmain\x2dcrypt.device est le « fichier » de configuration systemd désignant le périphérique bloc /dev/mapper/vgmain-crypt (on parle de device unit configuration). Cette chaîne de caractère est obtenu à l’aide de la commande systemd-escape:

      $ systemd-escape -p \(1)
          /dev/mapper/vgmain-crypt (2)
      
      1 -p indique que la valeur qui suit est un chemin de fichier.
      2 Chemin du périphérique bloc.
    • Activer le service cryptsetup-jl.service :

      # systemctl enable cryptsetup-jl.service

Et au final, content ?

Éh bien oui, ça fonctionne très bien. Mon répertoire utilisateur est déverrouillé lorsque j’ouvre une session utilisateur et est automatiquement reverouillé lorsque je la clos. Dans l’absolu on obtient un système moins sécurisé qu’avec un disque totalement chiffré, mais je pense que la facilité d’utilisation est également à prendre en compte. Le fait d’avoir un ordinateur sur lequel les données utilisateur — les plus critiques donc — sont protégées de manière transparente facilite selon moi l’adoption du chiffrement. Certains ergoteront qu’utiliser le même mot de passe pour le compte utilisateur et pour le chiffrement du volume présente un risque. Le risque me paraît acceptable dès lors que j’opte pour un mot de passe suffisamment long et non-trivial.

Il reste néanmoins un petit bémol pour en faire une solution totalement aboutie. Lorsque je serai amené à changer de mot de passe utilisateur, je devrai prendre soin de modifier manuellement la passphrase du volume chiffré pour les garder identiques. L’intégration d’eCryptfs à PAM était complète et prenait en charge ce cas d’utilisation. Il existe un projet poussant un peu plus loin l’intégration de cryptsetup à PAM, mais je n’ai pas pris le temps de le tester.

Malgré cette réserve, j’ai décidé de conserver cette configuration pour mon usage quotidien. Je ne change pas de mot de passe si souvent que ça et, le moment venu, ce sera une bonne opportunité de me replonger dans cet article. À mon moi du futur qui cherche comment changer la passphrase du volume chiffré, la réponse est là :

# cryptsetup luksChangeKey /dev/mapper/vgmain-crypt

Dernier point positif que je découvre seulement à la conclusion de cet article : même si ce n’était pas l’objectif recherché, peut-être ai-je gagné en confort d’utilisation au quotidien, tant LUKS/dm-crypt semble une solution plus performante qu’eCryptfs.


1. block device dans la langue de Shakespeare, mais j’essaie d’employer des termes français quand c’est possible.
2. Mio pour « mébioctet ». 1 Mio = 220 octets = 1 048 576 octets, alors que 1 Mo = 106 octets = 1 000 000 octets. Voir https://fr.wikipedia.org/wiki/Octet.
3. Voir les codes hexadécimaux des différents types de partitions : https://askubuntu.com/questions/703443/gdisk-hex-codes
4. Voir la description des métadonnées cryptographique : https://wiki.archlinux.org/index.php/Data-at-rest_encryption#Cryptographic_metadata
5. Voir article Wikipédia sur la fonction de dérivation de clé Argon2 : https://fr.wikipedia.org/wiki/Argon2