Introduction ¶
Disk encryption using dm-crypt
is a reliable way to protect data. However, in extreme situations, it may be necessary to instantly and irreversibly destroy encryption keys to render the data inaccessible. The nuke mechanism can be used for this purpose, activated by entering a special password.
In this article, we will explore how to implement nuke in dm-crypt
using initramfs, cryptsetup
, and mkpasswd
.
The developed hook is capable of:
- Decrypting the disk using data from a file.
- Prompting for a password if the file is not found.
- Deleting all keys if the entered password is a nuke password.
Important! This setup works on Ubuntu 22.04.5. Other distributions might require different configurations. In any case, here is an article on recovering Grub.
Environment Setup ¶
Before starting, ensure that the necessary utilities are installed:
1
2
| sudo apt update
sudo apt install cryptsetup initramfs-tools whois
|
Adding mkpasswd
Support in initramfs ¶
The nuke mechanism requires mkpasswd
, which is used to verify the entered password. We need to add it to initramfs by creating a hook:
File /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)"
|
Make it executable:
1
| sudo chmod +x /etc/initramfs-tools/hooks/mkpasswd
|
Creating the decrypt_smart_nuke
Script ¶
File /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
|
Make it executable:
1
| sudo chmod +x /usr/lib/cryptsetup/scripts/decrypt_smart_nuke
|
Boot Configuration ¶
Edit /etc/default/grub
and add the cryptdevice
argument:
1
| GRUB_CMDLINE_LINUX_DEFAULT="... cryptdevice=UUID=bb85fe0a-4ef7-49c4-b15c-fb613d51e9ef:cryptroot"
|
Then add the following line to /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
|
To generate a hash for the nuke password:
1
| mkpasswd -m sha512crypt --rounds=5000
|
Updating initramfs ¶
After making all changes, update initramfs and grub:
1
2
| sudo update-initramfs -u
sudo update-grub2
|
Conclusion ¶
Now, if the nuke password is entered at boot, the script will destroy the encryption keys, making the data inaccessible. This method is suitable for critical situations requiring rapid and irreversible data protection.
Additionally, the system will decrypt itself automatically if the key file is present. If needed, the USB drive can be removed, prompting the system for a password.
Backup your LUKS header before enabling this!
1
| sudo cryptsetup luksHeaderBackup /dev/sda1 --header-backup-file ./backup.luks
|