Featured image of post Clever Disk Encryption with dm-crypt Nuke

Clever Disk Encryption with dm-crypt Nuke

How to use the nuke mechanism to destroy encryption keys in dm-crypt

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:

  1. Decrypting the disk using data from a file.
  2. Prompting for a password if the file is not found.
  3. 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
Licensed under Apache License, Version 2.0
Last updated on Jan 16, 2025 14:26 +0200
All rights reserved
Built with Hugo
Theme Stack designed by Jimmy