Безопасная эксплуатация ноутбуков, или Защита мастер-ключа LUKS с помощью пользовательского ключа на USB-накопителе

Как мы уже знаем из первой части, система LUKS подбирает параметры хеширования ключей таким образом, чтобы для проверки одной парольн��й фразы нужно было не менее секунды, в связи с чем для взлома пароля длиной 8 символов требуется более ста миллионов лет. Однако рост производительности CPU/GPU и развитие квантовых технологий может привести к тому, что лет через десять текущие оценки окажутся неактуальными, поэтому с учетом будущих угроз длину пароля можно увеличить, и LUKS позволяет использовать фразы длиной до 512 символов. Тем не менее, каждый дополнительный символ существенно усложняет пользовательский опыт и повышает шансы того, что пользователь просто забудет свой пароль и потеряет доступ к данным. Сегодня мы покажем, как можно защитить мастер-ключ LUKS с помощью случайного пользовательского ключа, который трудно подобрать, легко потерять и невозможно забыть.


Чтобы не вводить идентификатор системного диска вручную, запишем его значение в переменную UUID:

UUID=$(sudo blkid --match-token TYPE=crypto_LUKS --output value -s UUID)
echo $UUID
Результат выполнения команд...
localadmin@pc:~$ UUID=$(sudo blkid --match-token TYPE=crypto_LUKS --output value -s UUID)
localadmin@pc:~$ echo $UUID
453fa939-a585-4e1f-a030-b11991a78d3f

Парольные фразы могут быть длиной до 512 символов, а файл ключа может быть размером до 8 мегабайт, но учитывая то, что для шифрования мастер-ключа в конечном итоге используется ключ шифрования длиной 256 бит, который мы получаем методом многократного хеширования, создавать случайные ключи размером более 32 байт не имеет практического смысла. Для генерации ключа воспользуемся утилитой dd и генератором случайных чисел urandom:

dd if=/dev/urandom bs=1 count=32 > $UUID.lek

Добавим ключ в заголовок LUKS с помощью команды luksAddKey. Утилите cryptsetup потребуется извлечь мастер-ключ, для чего она запросит одну из ранее установленных парольных фраз.

sudo cryptsetup luksAddKey /dev/sda5 $UUID.lek

Отметим, что преимуществом случайного ключа является то, что он обладает крайне высокой энтропией, поэтому подбирать его не имеет смысла и проще тогда уж сразу перейти к перебору 2256 комбинаций в попытке взломать один из слотов заголовка LUKS.

Чтобы удостовериться, что ключ был успешно добавлен, можно воспользоваться командой test-passphrase. Добавим в цепочку вызовов утилиту time, чтобы посчитать время выполнения запроса.

time sudo cryptsetup open --test-passphrase /dev/sda5 \
  --key-file=$UUID.lek && echo 'Ключ подошел'

Поскольку в заголовке у нас три ключа, а проверяемый ключ находится последним по списку, для выполнения операции требуется не менее трех секунд:

localadmin@pc-1:~$ time sudo cryptsetup open --test-passphrase /dev/sda5 \
> --key-file=$UUID.lek && echo 'Ключ подошел'
real 0m4,361s
user 0m12,697s
sys 0m0,462s
Ключ подошел

Скопируем файл ключа на внешний USB-накопитель с разметкой vfat, чтобы этот ключ можно было использовать для разблокирования системного диска. Это пока еще не Рутокен от компании «Актив», но всему свое время.

sudo mount /dev/sdb1 /mnt
sudo cp $UUID.lek /mnt

Добавим опцию keyscript в конец строки из файла /etc/crypttab, чтобы сист��ма запрашивала пользовательский ключ с помощью нашего кастомного скрипта:

# Удалим параметр keyscript из конца строк, если он есть
sudo sed -i 's|,keyscript=.*||' /etc/crypttab

# Добавим параметр keyscript с нужным нам значением
sudo sed -i 's|$|,keyscript=/bin/unlock_luks_with_usb|' /etc/crypttab

# Проверим результат
cat /etc/crypttab
Результат выполнения команд...
localadmin@pc:~$ sudo sed -i 's|$|,keyscript=/bin/unlock_luks_with_usb|' /etc/crypttab 
localadmin@pc:~$ cat /etc/crypttab 
sda5_crypt UUID=453fa939-a585-4e1f-a030-b11991a78d3f none luks,discard,keyscript=/bin/unlock_luks_with_usb

Каждая строка из файла /etc/crypttab имеет следующий формат:

<имя_раздела> <UUID_диска> <имя_ключа> <параметры>

По результатам своей работы скрипт должен передать в стандартный поток вывода набор байт, соответствующий пользовательскому ключу или его парольной фразе. В скрипте можно использовать следующие переменные:

  • $CRYPTTAB_NAME – имя раздела, первый параметр в строке монтирования шифрованного диска в файле /etc/crypttab . В приведенном примере sda5_crypt;

  • $CRYPTTAB_SOURCE – путь к шифрованному диску. В приведенном примере /dev/sda5;

  • $CRYPTTAB_KEY – имя пользовательского ключа, третий параметр. В приведенном примере none;

  • $CRYPTTAB_OPTIONS – опции монтирования, четвертый параметр. В приведенном примере «luks,discard,keyscript=/bin/unlock_luks_with_usb»;

  • $CRYPTTAB_TRIED – количество предыдущих попыток монтирования шифрованного диска (счет идет до тех пор, пока не будет достигнуто максимально допустимое количество попыток).

Создадим скрипт unlock_luks_with_usb со следующим набором команд:

Файл /bin/unlock_luks_with_usb

#!/bin/sh

set -e # Делаем выход из скрипта при ошибке
 
# Переключаемся в текстовый режим и устанавливаем кириллический шрифт
/usr/bin/plymouth hide-splash
setfont /usr/share/consolefonts/CyrSlav-Fixed16.psf.gz >&2
 
# Возвращаем графический интерфейс
/usr/bin/plymouth show-splash

mkdir -p /mnt
UUID=$(blkid -s UUID -o value $CRYPTTAB_SOURCE)
for USBPARTITION in /dev/disk/by-id/usb-*-part1; do
    USBDEVICE=$(readlink -f $USBPARTITION)
    if mount -t vfat $USBDEVICE /mnt -o iocharset=cp866 2>/dev/null; then
        if [ -e /mnt/$UUID.lek ]; then
            cat /mnt/$UUID.lek
            umount $USBDEVICE

            /usr/bin/plymouth display-message --text="Пользовательский ключ успешно извлечен, для продолжения загрузки требуется извлечь USB-токен..."
            while [ -e $USBPARTITION ]; do
                sleep 1
            done
            /usr/bin/plymouth display-message --text="USB-накопитель извлечен, продолжаем загрузку"

            exit
        fi
        umount $USBDEVICE
    fi
done
/usr/bin/plymouth display-message --text="Вы можете нажать клавишу <Enter>, чтобы переключиться на загрузку с помощью USB-накопителя"
/usr/bin/plymouth ask-for-password --prompt="Введите пароль"

Логику работы скрипта можно описать несколькими тезисами.

  • Сначала скрипт проверяет, подключен ли к компьютеру USB-накопитель, в корне которого находится файл <UUID>.lek, где UUID соответствует идентификатору системного диска.

  • Если LEK-файл будет найден, то:

    • скрипт с помощью утилиты cat передаст содержимое этого файла в стандартный поток вывода и завершит свою работу;

    • далее, если ключ подойдет, то загрузчик разблокирует диск и продолжит загрузку операционной системы;

    • если же ключ из LEK-файла окажется некорректным, то загрузчик предпримет еще одну попытку монтирования системного диска, поэтому скрипт будет запущен еще раз.

  • Если скрипт не найдет LEK-файл, то:

    • скрипт предложит пользователю ввести парольную фразу вручную с помощью команды ask-for-password;

    • если парольная фраза окажется некорректной или пользователь просто нажмет клавишу <Enter>, то загрузчик предпримет еще одну попытку монтирования диска, поэтому скрипт будет запущен еще раз.

По результатам опроса участников фокус-группы из нашего профессионального сообщества более 70% инженеров считают, что для продолжения загрузки нужно предлагать или даже требовать извлечения USB-устройства, на котором находится ключ. Если вы придерживаетесь иного мнения, то удалите строки 21-25 между вызовом команд display-message.

Сделаем скрипт исполняемым:

sudo chmod +x /bin/unlock_luks_with_usb

Для работы скрипта в образ initramfs требуется добавить несколько файлов:

  • CyrSlav-Fixed16.psf.gz — шрифт с поддержкой кириллических символов для возможности использования уведомлений на русском языке;

  • setfont — утилита, которая позволяет установить в консоли произвольный шрифт.

Для добавления файлов в образ initramfs нужно создать хук unlock_luks_with_usb_hook, который будет выполняться каждый раз при сборке образа:

Файл /etc/initramfs-tools/hooks/unlock_luks_with_usb_hook

#!/bin/sh
PREREQ=""
prereqs()
{
     echo "$PREREQ"
}

add_file()
{
    # Удаляем файл, если такой уже есть, поскольку функция copy_exec не умеет перезаписывать существующие файлы
    rm -f ${DESTDIR}$1
    # Добавляем указанный файл в initramfs
    copy_exec $1 $1
}

case $1 in
prereqs)
     prereqs
     exit 0
     ;;
esac

# Подключаем скрипт с функцией copy_exec
. /usr/share/initramfs-tools/hook-functions

# Добавляем кириллический шрифт и утилиту для его установки в консоли
add_file /usr/share/consolefonts/CyrSlav-Fixed16.psf.gz
add_file /usr/bin/setfont

Сделаем файл хука исполняемым:

sudo chmod +x /etc/initramfs-tools/hooks/unlock_luks_with_usb_hook

Для возможности монтирования USB-накопителей на ранних стадиях загрузки системы в ядро initramfs нужно включить несколько дополнительных модулей, для чего в файл modules требуется добавить следующие строки: 

Файл /etc/initramfs-tools/modules

...
# Модуль для работы с USB-накопителями
usb_storage

# Модуль для работы с файловой системой VFAT
vfat

Соберем образ initramfs:

sudo update-initramfs -u

Если вы еще не читали первую часть статьи, то выполните дополнительно следующие команды, чтобы настроить plymouth. Мы рекомендуем использовать тему spinner, которая отображает текстовые сообщения в центре экрана под индикатором загрузки, что делает их достаточно заметными.

sudo apt install --yes plymouth-themes
sudo plymouth-set-default-theme spinner --rebuild-initrd
sudo sed -i 's|"quiet|"splash quiet|' /etc/default/grub
sudo update-grub

Перезагрузим виртуальную машину, дождемся приглашения, подключим USB-накопитель и нажмем клавишу <Enter> как показано на рисунке 7 (сквозная нумерация рисунков, начало см. в первой части). При необходимости пользователь сможет разблокировать диск без USB-накопителя, используя сложный пароль, который был использован при установке системы.

Рисунок 7 – Приглашение подключить USB-накопитель с ключом
Рисунок 7 – Приглашение подключить USB-накопитель с ключом

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

В третьей части мы расскажем о наиболее перспективном по нашему мнению способе решения поставленной задачи — защите мастер-ключа с помощью USB-токена на примере Рутокен ЭЦП 3.0. Такие устройства позволяют, с одной стороны, использовать простые PIN-коды, а с другой — обеспечить надежную защиту от перебора, поскольку USB-токены позволяют установить ограничение на количество попыток ввода PIN-кода.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
А как вы думаете, если для разблокирования системного диска был использован ключ, размещенный на USB-накопителе, нужно ли останавливать загрузку операционной системы до тех пор, пока этот накопитель не будет извлечен из компьютера?
75%Да, нужно обязательно останавливать загрузку ОС, чтобы пользователи не забывали USB-накопитель в компьютере9
0%Да, нужно рекомендовать извлечь USB-накопитель, но позволить продолжить загрузку по нажатию клавиши0
25%Нет, не нужно требовать извлечения USB-накопителя, т.к. его можно использовать не только для хранения ключа LUKS3
Проголосовали 12 пользователей. Воздержался 1 пользователь.