Featured image of post Хитрое шифрование диска dm-crypt nuke

Хитрое шифрование диска dm-crypt nuke

Как использовать механизм nuke для уничтожения ключей шифрования в dm-crypt

Введение

Шифрование диска с использованием dm-crypt — это надёжный способ защиты данных. Однако в экстремальных ситуациях может потребоваться мгновенное и необратимое уничтожение ключей шифрования, чтобы данные стали недоступными. Для этого можно использовать механизм nuke, который активируется при вводе специального пароля.

В этой статье рассмотрим, как реализовать nuke в dm-crypt, используя initramfs, cryptsetup и mkpasswd.

Разработанный хук умеет:

  1. Расшифровывать диск используя данные с файла
  2. Если файл не найден, спрашивает пароль
  3. Если пароль это nuke пароль - удаляет все ключи

Важно! У меня это работает на Ubuntu 22.04.5, есть большая вероятность, что на других дистрибутивах нужно будет сделать что-то по другому. В любом случае, вот статья по восcтановлению Grub.

Подготовка окружения

Перед началом убедитесь, что установлены необходимые утилиты:

1
2
sudo apt update
sudo apt install cryptsetup initramfs-tools whois

Добавление поддержки mkpasswd в initramfs

Для работы механизма nuke требуется mkpasswd, который используется для проверки введённого пароля. Добавим его в initramfs, создав хук:

Файл /etc/initramfs-tools/hooks/mkpasswd:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/sh
set -e

if [ "$1" = "prereqs" ]; then
  exit 0
fi

. /usr/share/initramfs-tools/hook-functions

if ! command -v mkpasswd 1>/dev/null; then
  echo "error: mkpasswd executable is not found, install it." 1>&2
  exit 1
fi

copy_exec "$(command -v mkpasswd)"

Сделаем его исполняемым:

1
sudo chmod +x /etc/initramfs-tools/hooks/mkpasswd

Создание скрипта decrypt_smart_nuke

Файл /usr/lib/cryptsetup/scripts/decrypt_smart_nuke:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/bin/sh
set -e

KEYFILE_ENABLED='false'
NUKE_ENABLED='false'

KEYFILE_PATH=''
KEYFILE_OFFSET=0
KEYFILE_SIZE=''

NUKE_PASSWORD=''

CRYPTTAB_TRIED="${CRYPTTAB_TRIED?:CRYPTTAB_TRIED is not defined}"
CRYPTTAB_NAME="${CRYPTTAB_NAME?:CRYPTTAB_NAME is not defined}"
CRYPTTAB_SOURCE="${CRYPTTAB_SOURCE?:CRYPTTAB_SOURCE is not defined}"
ASKPASS='/lib/cryptsetup/askpass'

#  ┬─┐┬ ┐┌┐┐┌─┐┌┐┐o┌─┐┌┐┐┐─┐
#  ├─ │ │││││   │ ││ ││││└─┐
#  ┘  ┘─┘┘└┘└─┘ ┘ ┘┘─┘┘└┘──┘

# Logging helpers. Send the argument list to plymouth(1), or fold it
# and print it to the standard error.
message() {
  local IFS=' '
  if [ "${0#/scripts/}" != "$0" ] && [ -x /bin/plymouth ] && plymouth --ping; then
    plymouth message --text="cryptsetup: $*"
  elif [ ${#*} -lt 70 ]; then
    echo "cryptsetup: $*" 1>&2
  else
    # use busybox's fold(1) and sed(1) at initramfs stage
    echo "cryptsetup: $*" | fold -s | sed '1! s/^/    /' 1>&2
  fi
  return 0
}

parse_options() {
  local IFS="," key value regex
  for option in $1; do
    key="${option%%=*}"
    value="${option#*=}"
    case "$key" in
      keyfile-path)
        if [ ! -e "$value" ]; then
          message "warning: keyfile at $value does not exist"
        else
          export KEYFILE_ENABLED="true"
          export KEYFILE_PATH="$value"
          if [ -z "$KEYFILE_SIZE" ]; then
            export KEYFILE_SIZE="$(wc -c "$KEYFILE_PATH" | awk '{print $1}')"
          fi
        fi
        ;;
      keyfile-offset)
        export KEYFILE_OFFSET="$value"
        ;;
      keyfile-size)
        export KEYFILE_SIZE="$value"
        ;;
      nuke)
        regex='(\$[^$]+){3,}'
        if ! echo "$value" | grep -qE "$regex"; then
          message "$(printf "error: password hash does not match the regex '%s': %s\n" "$regex" "$value")"
          exit 1
        fi
        export NUKE_ENABLED="true"
        export NUKE_PASSWORD="$value"
        ;;
      none) true;;
      *)
        message "error: unknown option $key: $value" 1>&2
        exit 1
        ;;
    esac
  done
}

read_keyfile() {
  if [ "$KEYFILE_ENABLED" != 'true' ] || [ "$CRYPTTAB_TRIED" -ne 0 ]; then
    return
  fi
  dd if="$KEYFILE_PATH" skip="$KEYFILE_OFFSET" bs=1 count="$KEYFILE_SIZE" 2>/dev/null
  exit 0
}

nuke() {
  echo YES | cryptsetup erase "$CRYPTTAB_SOURCE"
  echo 1>&2 '''
                               ________________
                          ____/ (  (    )   )  \___
                         /( (  (  )   _    ))  )   )\
                       ((     (   )(    )  )   (   )  )
                     ((/  ( _(   )   (   _) ) (  () )  )
                    ( (  ( (_)   ((    (   )  .((_ ) .  )_
                   ( (  )    (      (  )    )   ) . ) (   )
                  (  (   (  (   ) (  _  ( _) ).  ) . ) ) ( )
                  ( (  (   ) (  )   (  ))     ) _)(   )  )  )
                 ( (  ( \ ) (    (_  ( ) ( )  )   ) )  )) ( )
                  (  (   (  (   (_ ( ) ( _    )  ) (  )  )   )
                 ( (  ( (  (  )     (_  )  ) )  _)   ) _( ( )
                  ((  (   )(    (     _    )   _) _(_ (  (_ )
                   (_((__(_(__(( ( ( |  ) ) ) )_))__))_)___)
                   ((__)        \\||lll|l||///          \_))
                            (   /(/ (  )  ) )\   )
                          (    ( ( ( | | ) ) )\   )
                           (   /(| / ( )) ) ) )) )
                         (     ( ((((_(|)_)))))     )
                          (      ||\(|(|)|/||     )
                        (        |(||(||)||||        )
                          (     //|/l|||)|\\ \     )
                        (/ / //  /|//||||\\  \ \  \ _)
-----------------------------!!! YOU ARE NUKED !!!-----------------------------
'''
  message "Data destroyed! They may try to extract information from you, but there's nothing more you can do. Good luck!"
  exit 1
}

read_interactive() {
  local password salt
  password="$($ASKPASS "Enter passphrase for $CRYPTTAB_NAME ($CRYPTTAB_SOURCE): ")"
  if [ "$NUKE_ENABLED" = 'true' ]; then
    salt="$(echo "$NUKE_PASSWORD" | cut -d$ -f3)"
    for method in bcrypt bcrypt-a sha256crypt sha512crypt md5crypt; do
      if mkpasswd -m "$method" -S "$salt" "$password" 2>/dev/null | grep -qF -- "$NUKE_PASSWORD"; then
        nuke
      fi
    done
  fi
  printf "%s" "$password"
  exit 0
}

#  ┌─┐┬ ┬┬─┐┌─┐┬┌ ┐─┐
#  │  │─┤├─ │  ├┴┐└─┐
#  └─┘┘ ┴┴─┘└─┘┘ ┘──┘

if ! command -v mkpasswd 1>/dev/null 2>&1; then
  message "error: mkpasswd binary is not found"
  exit 1
elif [ ! -f "$ASKPASS" ]; then
  message "error: $ASKPASS does not exist"
  exit 1
elif [ ! -e "$CRYPTTAB_SOURCE" ]; then
  message "error: crypttab source device does not exist"
  exit 1
fi

#  ┌┌┐┬─┐o┌┐┐
#  ││││─┤││││
#  ┘ ┘┘ ┘┘┘└┘

parse_options "$1"
read_keyfile
read_interactive

Сделаем его исполняемым:

1
sudo chmod +x /usr/lib/cryptsetup/scripts/decrypt_smart_nuke

Настройка загрузки

В моём случае, у меня зашифрован / и не зашифрован /boot. Прежде всего, нужно добавить cryptdevice аргумент в /etc/default/grub. UUID можно получить из вывода blkid (берём UUID зашифрованного раздела).

1
GRUB_CMDLINE_LINUX_DEFAULT="... cryptdevice=UUID=bb85fe0a-4ef7-49c4-b15c-fb613d51e9ef:cryptroot"

Теперь, нужно добавить в /etc/crypttab указание использовать таш скрипт.

1
cryptroot UUID=bb85fe0a-4ef7-49c4-b15c-fb613d51e9ef keyfile-path=/dev/mmcblk0,keyfile-offset=8932578598,keyfile-size=11045,nuke=HASH luks,discard,keyscript=decrypt_smart_nuke

Описание аргументов:

  1. cryptroot - имя раздела после расшифровки. В данном случае он появится в системе как /dev/mapper/cryptroot. Должно совпадать с тем, что мы написали в /etc/default/grub.
  2. UUID=bb85fe0a-4ef7-49c4-b15c-fb613d51e9ef - то же, что мы прописали в /etc/default/grub.
  3. Всё третье поле это аргумент к скрипту:
    1. keyfile-path=/dev/mmcblk0 - путь к файлу или устройству откуда читать пароль, в моём случае это SD карта
    2. keyfile-offset=4194836274 - отступ в байтах от начала файла, если не указано - читать с начала.
    3. keyfile-size=7333 - длинна последовательности для чтения, если не указано - читать до конца.
    4. nuke=HASH - хеш пароля самоуничтожения.
  4. luks,discard,keyscript=decrypt_smart_nuke - опции для cryptsetup, где мы, среди всего прочего, указываем имя скрипта для получения пароля.

Замените HASH на хэш пароля, который можно получить вот так.

1
mkpasswd -m sha512crypt --rounds=5000

И keyfile, и nuke опциональны, можно не указывать их и скрипт отработает только на интерактивный ввод.

Файле для считывания ключа

Тут идея в следующем. У меня на ноутбуке есть SD Card Reader, так вот он всегда живёт с заглушкой. Почему бы, подумал я, его не использовать для хранения ключа? Есть два интересных варианта:

  1. Сделать раздел на флешке, но оставить буквально один мегабайт до или после него. Забить случайными данными и использовать этот кусок как ключ - незаметно. Сам раздел использовать с пользой.
  2. Забить всю флешку случайными данными и использовать случайный адрес и длину последовательности.

Вот второй вариант я и выбрал. Да, сторонний наблюдатель поймёт, что тут что-то не чисто, да, флешку нельзя использовать по прямому назначению, но поди угадай тот адрес начала и длину последовательности.

И ещё, можно использовать другие, более безопасные варианты указаний пути к разделу:

  1. /dev/disk/by-id/…
  2. /dev/disk/by-uuid/…
  3. /dev/disk/by-label/…

У меня просто SD Card Reader один физически и больше не планируется.

Обновление initramfs

После внесения всех изменений обновите initramfs и grub.

1
2
sudo update-initramfs -u
sudo update-grub2

Итог

Теперь, если при загрузке системы ввести nuke-пароль, скрипт уничтожит ключи шифрования и сделает данные недоступными. Этот метод подходит для критических ситуаций, требующих быстрой и необратимой защиты информации.

Кроме того, система будет расшифровываться сама, а если нужно куда-то поехать с ноутбуком, то флешку можно вытащить и он будет спрашивать пароль.

Будьте осторожны при использовании данного механизма и имейте бекап.

1
sudo cryptsetup luksHeaderBackup /dev/sda1 --header-backup-file ./backup.luks
Licensed under Apache License, Version 2.0
Обновлено янв. 16, 2025 14:26 +0200
All rights reserved
Создано при помощи Hugo
Тема Stack, дизайн Jimmy