Введение ¶
Шифрование диска с использованием dm-crypt
— это надёжный способ защиты данных. Однако в экстремальных ситуациях может потребоваться мгновенное и необратимое уничтожение ключей шифрования, чтобы данные стали недоступными. Для этого можно использовать механизм nuke, который активируется при вводе специального пароля.
В этой статье рассмотрим, как реализовать nuke в dm-crypt
, используя initramfs, cryptsetup
и mkpasswd
.
Разработанный хук умеет:
- Расшифровывать диск используя данные с файла
- Если файл не найден, спрашивает пароль
- Если пароль это 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
|
Описание аргументов:
cryptroot
- имя раздела после расшифровки. В данном случае он появится в системе как /dev/mapper/cryptroot. Должно совпадать с тем, что мы написали в /etc/default/grub.UUID=bb85fe0a-4ef7-49c4-b15c-fb613d51e9ef
- то же, что мы прописали в /etc/default/grub.- Всё третье поле это аргумент к скрипту:
keyfile-path=/dev/mmcblk0
- путь к файлу или устройству откуда читать пароль, в моём случае это SD картаkeyfile-offset=4194836274
- отступ в байтах от начала файла, если не указано - читать с начала.keyfile-size=7333
- длинна последовательности для чтения, если не указано - читать до конца.nuke=HASH
- хеш пароля самоуничтожения.
luks,discard,keyscript=decrypt_smart_nuke
- опции для cryptsetup, где мы, среди всего прочего, указываем имя скрипта для получения пароля.
Замените HASH
на хэш пароля, который можно получить вот так.
1
| mkpasswd -m sha512crypt --rounds=5000
|
И keyfile, и nuke опциональны, можно не указывать их и скрипт отработает только на интерактивный ввод.
Файле для считывания ключа ¶
Тут идея в следующем. У меня на ноутбуке есть SD Card Reader, так вот он всегда живёт с заглушкой. Почему бы, подумал я, его не использовать для хранения ключа? Есть два интересных варианта:
- Сделать раздел на флешке, но оставить буквально один мегабайт до или после него. Забить случайными данными и использовать этот кусок как ключ - незаметно. Сам раздел использовать с пользой.
- Забить всю флешку случайными данными и использовать случайный адрес и длину последовательности.
Вот второй вариант я и выбрал. Да, сторонний наблюдатель поймёт, что тут что-то не чисто, да, флешку нельзя использовать по прямому назначению, но поди угадай тот адрес начала и длину последовательности.
И ещё, можно использовать другие, более безопасные варианты указаний пути к разделу:
- /dev/disk/by-id/…
- /dev/disk/by-uuid/…
- /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
|