Защитное преобразование и защита мастер-ключа с помощью парольных фраз

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

Один из способов решения указанной проблемы для обеспечения безопасности инфраструктуры домена ALD Pro мы видим в использовании защитного преобразования данных на диске, поэтому я обратился к своему хорошему товарищу Кириллу @mkirya, эксперту по вопросам аутентификации и электронной подписи, чтобы максимально детально разобраться в этом вопросе. За помощь в подготовке материала и отладке скриптов респект моему коллеге Вадиму @NotMusk, старшему инженеру продуктовой команды ALD Pro, и Евгению @rtresme, руководителю отдела R&D Компании «Актив».

В этом цикле статей мы подробно рассмотрим технологию LUKS с позиции системного администрирования и способы защиты мастер-ключа, в том числе и с использованием алгоритмов ГОСТ Р 34.10-2012 на Рутокен ЭЦП 3.0. Материал прошел обсуждение в фокус-группе нашего сообщества ALD Proфессионалов и будет включен в содержание открытого курса по службе каталога ALD Pro. Возможности повысить квалификацию в объеме 16 академ. часов не обещаем, но вкусных буковок будет много.


Операционную систему Linux можно установить на диск с защитным преобразованием, что позволит избежать несанкционированного доступа к данным, даже если злоумышленник получит прямой физический доступ к устройству. По сути эта дополнительная мера защиты является аналогом того, что в Windows именуется Bitlocker. Большинство современных дистрибутивов Linux поддерживают такой сценарий развертывания из коробки, поэтому достаточно просто выбрать соответствующий пункт в мастере установки, как показано на рисунке 1.

Рисунок 1 — Выбор разметки LVM с использованием защитного преобразования
Рисунок 1 — Выбор разметки LVM с использованием защитного преобразования

При загрузке системы, которая установлена на диск с защитным преобразованием, будет появляться приглашение для ввода парольной фразы. Если нажать клавишу <Esc>, то унылое классическое окно терминала сменится графическим интерфейсом plymouth, но тема оформления, установленная по умолчанию, вряд ли вас впечатлит.

Рисунок 2 — Приглашение ввода пароля для разблокирования системного диска
Рисунок 2 — Приглашение ввода пароля для разблокирования системного диска

Приложение plymouth было названо в честь города Плимут, штат Массачусетс. Приложение запускается на ранней стадии процесса загрузки еще до монтирования корневой файловой системы и реализует графический интерфейс для взаимодействия с пользователем и отображения процесса загрузки. Текущую тему оформления plymouth можно узнать с помощью утилиты plymouth-set-default-theme:

localadmin@pc:~$ sudo plymouth-set-default-theme
text

Список всех тем, доступных в системе, можно получить с помощью ключа -l (--list):

localadmin@pc:~$ sudo plymouth-set-default-theme --list
details
text
tribar

Чтобы получить дополнительный набор тем, установим пакет plymouth-themes:

sudo apt install plymouth-themes
Список тем оформления, доступных после установки дополнительного пакета...
localadmin@pc:~$ sudo plymouth-set-default-theme --list
bgrt
details
fade-in
glow
script
solar
spinfinity
spinner
text
tribar

Установим по умолчанию тему glow из дополнительного комплекта. Команду нужно вызывать с ключом -R (--rebuild-initrd), чтобы была выполнена повторная сборка образа initrd.img:

sudo plymouth-set-default-theme glow --rebuild-initrd

Для того, чтобы графический интерфейс plymouth открывался сразу без нажатия клавиши <Esc>, добавим опцию splash в параметр GRUB_CMDLINE_LINUX_DEFAULT из файла /etc/default/grub:

sudo sed -i 's|"quiet|"splash quiet|' /etc/default/grub
cat /etc/default/grub | grep quite
Результат выполнения команд...
localadmin@pc:~$ sudo sed -i 's/"quite/"splash quite/' /etc/default/grub
localadmin@pc:~$ cat /etc/default/grub | grep quite
GRUB_CMDLINE_LINUX_DEFAULT="splash quite net.ifnames=0 parsec.max_ilev=63"

Обновим конфигурацию GRUB, чтобы изменения вступили в силу:

sudo update-grub

Ну вот, теперь приглашение для ввода пароля стало намного привлекательнее (рис. 3).

Рисунок 3 — Внешний вид plymouth при установке темы оформления glow
Рисунок 3 — Внешний вид plymouth при установке темы оформления glow

Далее вы можете ознакомиться со скриншотами базовых тем оформления plymouth.

Тема bgrt...

Тема bgrt (англ. Boot Graphics Resource Table – таблица загрузки графических ресурсов) является вариацией темы spinner, которая отображает логотип производителя техники OEM, если он будет доступен.

Тема details...

Тема details не имеет графического интерфейса и выводит подробную информацию о ходе загрузки операционной системы в текстовом формате.

Тема fade-in...

Тема fade-in предлагает простой интерфейс, в котором загрузка выполняется на фоне мерцающих звезд и плавно загорающегося логотипа.

Тема glow...

Тема glow предлагает корпоративный стиль оформления с прогресс-баром в виде круговой диаграммы, за которым появляется светящийся логотип.

Тема script...

Тема script является примером для создания кастомизированного интерфейса загрузки с классическим прогресс-баром в виде горизонтальной полосы.

Тема solar...

Тема solar является космической темой с ярко-синей вспыхивающей звездой.

Тема spinfinity...

Тема spinfinity (англ. spin finity - вращающаяся бесконечность) предлагает простой интерфейс с индикатором загрузки в виде знака бесконечности в центре экрана.

Тема spinner...

Тема spinner предлагает простую тему оформления с круглым индикатором загрузки, который вращается в центре экрана.

Тема text...

Тема text предлагает интерфейс в виде текстовой строки в центре экрана с приглашением для ввода пароля. Индикатор загрузки представляет собой три точки, загорающиеся по очереди.

Тема tribar...

Тема tribar является вариацией темы text, которая использует прогресс-бар в виде трехцветной горизонтальной полосы в нижней части экрана.

После загрузки системы вы сможете узнать, какой именно диск использует защитное преобразование, например, с помощью утилиты blkid (block device id):

localadmin@pc-1:~$ sudo blkid --match-token TYPE=crypto_LUKS --output device 
/dev/sda5

localadmin@pc-1:~$ sudo blkid --match-token TYPE=crypto_LUKS --output value -s UUID
453fa939-a585-4e1f-a030-b11991a78d3f

, где:

  • -t (--match-token) — выполняет поиск устройства по значению параметра. К наиболее распространенным параметрам относятся TYPE, LABEL и UUID;

  • -o (--output) — определяет формат вывода;

  • -s (--match-tag) — определяет список тегов, значения которых нужно отобразить для найденного устройства.

Дополнительную информацию об организации хранения данных вы можете получить с помощью утилиты lsblk (list block devices):

localadmin@pc:~$ lsblk
NAME                MAJ:MIN RM  SIZE RO TYPE  MOUNTPOINT
sda                   8:0    0   32G  0 disk  
├─sda1                8:1    0  487M  0 part  /boot
├─sda2                8:2    0    1K  0 part  
└─sda5                8:5    0 31,5G  0 part  
  └─sda5_crypt      253:0    0 31,5G  0 crypt 
    ├─pc--vg-root   253:1    0 30,5G  0 lvm   /
    └─pc--vg-swap_1 253:2    0  976M  0 lvm   
sr0                  11:0    1   51M  0 rom

На базовом уровне механизм защитного преобразования опирается на стандартную подсистему DM-Crypt, которая появилась в ядре Linux еще с версии 2.6 и обеспечивает сопоставление устройств (Device Mapper, DM), плюс непосредственно защитное преобразование данных (Crypt). Поверх этой подсистемы работает технология LUKS, которая обеспечивает универсальный способ организации хранения ключей в разметке дисков Linux (Linux Unified Key Setup-on-disk-format). В соответствии с LUKS данные диска защищаются случайно сгенерированным мастер-ключом, который в свою очередь защищается уже ключом пользователя. С первого взгляда кажется немного запутанно, не правда ли?

Технология LUKS была разработана для возможности использования сразу нескольких пользовательских ключей или парольных фраз. Данные на диске защищаются мастер-ключом, значение которого обычно не меняют в течении всего времени использования диска, поскольку изменение мастер-ключа потребует повторного преобразования всех данных блочного устройства, что является очень ресурсоемкой операцией (см. команду cryptsetup reencrypt). Мастер-ключ шифруется пользовательским ключом и сохраняется в одном из слотов заголовка LUKS, что позволяет использовать несколько пользовательских ключей и быстро менять их при необходимости (рис. 4).

Рисунок 4 — Доступ к мастер-ключу диска с помощью нескольких пользовательских ключей
Рисунок 4 — Доступ к мастер-ключу диска с помощью нескольких пользовательских ключей

В первой версии LUKS заголовок диска содержал бинарный блок данных с метаинформацией и 8 слотов для хранения пользовательских ключей. В современных системах используется вторая версия LUKS, в которой к бинарному блоку добавлена область для хранения дополнительной метаинформации в формате JSON, количество слотов увеличено до 32, а весь заголовок продублирован дважды для обеспечения надежности. Структура заголовка LUKS2 представлена на рисунке 5.

Рисунок 5 — Структура заголовка LUKS2
Рисунок 5 — Структура заголовка LUKS2

Бинарный заголовок включает следующие поля:

  • magic — уникальная строка, определяющая сигнатуру LUKS;

  • version — версия LUKS;

  • hdr_size — размер бинарного заголовка LUKS вместе с областью для хранения JSON-объекта с дополнительной метаинформацией;

  • sequid — счетчик изменений, который увеличивается каждый раз при обновлении заголовка LUKS. Система будет использовать резервный заголовок, если у него значение счетчика изменений окажется больше;

  • label — текстовая метка в формате ASCII, которая может использоваться в udev-правилах;

  • csum_alg — алгоритм для проверки контрольной суммы, которая покрывает как бинарный блок данных, так и последующую область JSON с дополнительной метаинформацией;

  • salt — случайное значение, которое является уникальным для любой из копий заголовка, что позволяет исключить дедупликацию секторов, содержащих метаинформацию LUKS;

  • uuid — уникальный идентификатор устройства, который может использоваться в udev-правилах;

  • subsystem — дополнительная метка с именем подсистемы, которая является владельцем шифрованного раздела LUKS. Поле может использоваться в udev-правилах;

  • hdr_offset — смещение заголовка от начала устройства (в байтах). Если смещение не совпадет с реальным положением заголовка на диске, то заголовок будет считаться некорректным;

  • csum — контрольная сумма, рассчитанная с использованием алгоритма, который указан в поле csum_algorithm.

Например, размер заголовка hdr_size представляет собой целое число размером 8 байт и хранится в нулевом секторе со смещением также в 8 байт. Извлечь эту информацию можно с помощью утилиты od следующим образом:

sudo od --address-radix n --endian=big --format d8 --skip-bytes 8 --read-bytes 8 /dev/sda5

, где:

  • -A (--address-radix) — определяет формат для вывода информации о смещении относительно начала файла. Параметр может принимать четыре значения:

    • d — смещение в числах десятичной системы счисления (decimal),

    • o — смещение в числах восьмеричной системе счисления (Octal),

    • h — смещение в числах шестнадцатеричной системе счисления (Hex),

    • n — без информации о смещении (None);

  • --endian — определяет прямой или обратный порядок байт. Параметр может принимать два значения:

    • big — порядок от старшего к младшему байту An-1 ... A0 (англ. big-endian — с большого конца),

    • little — от младшего к старшему A0 ... An-1 (англ. little-endian — с малого конца);

  • -t (--format) — определяет формат, в котором нужно представить данные. Значение d8 соответствует целому числу из 8 байт в десятичной системе счисления;

  • -j (--skip-bytes) — определяет количество байт, которые нужно пропустить от начала файла;

  • -N (--read-bytes) — определяет количество байт, которые нужно вычитать из файла;

  • /dev/sda5 — определяет имя файла.

Результат выполнения команды покажет значение 16384:

localadmin@pc-1:~$ sudo od --address-radix n --endian=big --format d8 --skip-bytes 8 --read-bytes 8 /dev/sda5
16384

Область JSON включает следующие разделы:

  • config — содержит общие параметры заголовка, например, размер JSON-объекта, размер области ключей и др.;

  • segments — объекты содержат информацию о зашифрованных областях данных, такую как размер кластера, алгоритм шифрования, значение вектора инициализации и др. На диске LUKS обычно находится только одна зашифрованная область, поэтому в заголовке описывается только один сегмент;

  • keyslots — объекты содержат дополнительную метаинформацию о пользовательских ключах, такую как тип ключа, его размер, приоритет, параметры хеширования и алгоритма шифрования для извлечения мастер-ключа;

  • digests — объекты содержат информацию для проверки извлеченного мастер-ключа. Каждому слоту должен быть назначен дайджест для проверки полученного значения;

  • tokens — объекты содержат дополнительные метаданные, необходимые для получения пользовательского ключа с использованием внешних систем. Например, может содержать пользовательский ключ, зашифрованный открытым ключом USB-токена.

Прочитать JSON-заголовок можно с помощью утилиты dd с помощью следующей команды:

sudo dd if=/dev/sda5 bs=1 skip=4096 count=$((16384 - 4096))

, где:

  • if — определяет имя файла, из которого нужно вычитать информацию;

  • bs — определяет размер блоков, с которыми будет работать утилита, значение указывается в количестве байт;

  • skip — определяет количество блоков, которые нужно пропустить с начала файла. Заголовок JSON начинается с конца нулевого сектора размером 4096 байт;

  • count — определяет количество блоков, которые нужно вычитать из файла. Размер JSON-заголовка можно рассчитать как разницу между размером всего заголовка метаданных hdr_size и размером одного кластера в 4096 байт, который выделяется под хранение бинарного заголовка данных.

Результат выполнения команды...
localadmin@pc-1:~$ sudo dd if=/dev/sda5 bs=1 skip=4096 count=$((16384 - 4096))

{"keyslots":{"0":{"type":"luks2","key_size":64,"af":{"type":"luks1","stripes":4000,"hash":"sha256"},"area":{"type":"raw","offset":"32768","size":"258048","encryption":"aes-xts-plain64","key_size":64},"kdf":{"type":"argon2i","time":4,"memory":587809,"cpus":4,"salt":"yY8WFQm4HOYgkm3Wu5NdptnGThVT4UV3FjL86SRceCM="}}},"tokens":{},"segments":{"0":{"type":"crypt","offset":"16777216","iv_tweak":"0","size":"dynamic","encryption":"aes-xts-plain64","sector_size":512}},"digests":{"0":{"type":"pbkdf2","keyslots":["0"],"segments":["0"],"hash":"sha256","iterations":169125,"salt":"ebZtI+Y44wWP9SI7itk6NkCLEx3FdDbTD3tIbabRTAI=","digest":"fUEFoCJ9XdtujdjQnlFOxRGgd7Pw+jNIwYQVz+17iRQ="}},"config":{"json_size":"12288","keyslots_size":"16744448"}}

12288+0 записей получено
12288+0 записей отправлено
12288 байт (12 kB, 12 KiB) скопирован, 0,0303519 s, 405 kB/s

Область ключей — это просто блоки данных (слоты), в которых находится мастер-ключ, зашифрованный ключом пользователя. Пользовательский ключ может представлять собой как бинарные данные, которые можно передать только в виде файла, так и обычную парольную фразу, которую можно ввести с клавиатуры. При этом никто не запрещает записать парольную фразу в файл, например, командой «echo -n 'Pa$$w0rd' > password.key», и использовать этот файл в качестве ключа.

Для управления ключами шифрованного диска LUKS можно использовать следующие команды утилиты cryptsetup:

  • luksAddKey <файл_устройства> [<файл_нового_ключа>] [--key-file=<файл_текущего_ключа>]

    Команда добавляет еще один ключ, хотя правильнее будет сказать, что она шифрует мастер-ключ диска новым ключом пользователя и сохраняет полученное значение в следующий свободный слот. Для того, чтобы утилита cryptsetup cмогла извлечь значение мастер-ключа из заголовка LUKS, ей с помощью параметра --key-file передается один из ранее установленных пользовательских ключей. Если файл ключа не будет определен через параметр, то утилита в интерактивном режиме запросит парольную фразу вместо ключа.

  • luksChangeKey <файл_устройства> [<файл_нового_ключа>] [--key-file=<файл_заменяемого_ключа>]

    Команда заменяет существующий ключ. Заменяемый ключ может быть предоставлен как парольная фраза в интерактивном режиме или передан как файл через параметр --key-file. Новый ключ может быть введен как парольная фраза в интерактивном режиме или прочитан из файла, имя которого можно передать через позиционный параметр.

  • luksRemoveKey <файл_устройства> [<файл_удаляемого_ключа>] [--key-file=<файл_удаляемого_ключа>]

    Команда удаляет ранее установленный ключ. Удаляемый ключ может быть предоставлен как парольная фраза в интерактивном режиме, а также его можно передать в виде файла через позиционный параметр или с помощью именованного параметра --key-file.

  • luksKillSlot <файл_устройства> [<номер_слота>] [--key-file=<файл_текущего_ключа>]

    Команда позволяет удалить любой ключ по номеру слота, используя для авторизации любой другой пользовательский ключ, который можно предоставить как пароль в интерактивном режиме или как ключ через параметр --key-file.

Добавим еще один пользовательский ключ к зашифрованному диску LUKS с помощью следующей команды:

sudo cryptsetup luksAddKey /dev/sda5

Утилита сначала запросит существующую парольную фразу, затем попросит два раза ввести новый пароль:

localadmin@pc-1:~$ sudo cryptsetup luksAddKey /dev/sda5
Введите любую существующую парольную фразу: 
Введите новую парольную фразу для слота ключа: 
Парольная фраза повторно:

Заголовок шифрованного диска и список установленных пользовательских ключей можно посмотреть с помощью команды luksDump утилиты cryptsetup, см. раздел Keyslots.

Результат выполнения команды luksDump...
localadmin@pc-1:~$ sudo cryptsetup luksDump /dev/sda5
LUKS header information
Version:        2
Epoch:          4
Metadata area:  16384 [bytes]
Keyslots area:  16744448 [bytes]
UUID:           453fa939-a585-4e1f-a030-b11991a78d3f
Label:          (no label)
Subsystem:      (no subsystem)
Flags:          (no flags)

Data segments:
  0: crypt
        offset: 16777216 [bytes]
        length: (whole device)
        cipher: aes-xts-plain64
        sector: 512 [bytes]

Keyslots:
  0: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      argon2i
        Time cost:  4
        Memory:     587809
        Threads:    4
        Salt:       c9 8f 16 15 09 b8 1c e6 20 92 6d d6 bb 93 5d a6 
                    d9 c6 4e 15 53 e1 45 77 16 32 fc e9 24 5c 78 23 
        AF stripes: 4000
        AF hash:    sha256
        Area offset:32768 [bytes]
        Area length:258048 [bytes]
        Digest ID:  0
  1: luks2
        Key:        512 bits
        Priority:   normal
        Cipher:     aes-xts-plain64
        Cipher key: 512 bits
        PBKDF:      argon2i
        Time cost:  7
        Memory:     1048576
        Threads:    4
        Salt:       3a 52 dc c0 0f 82 6f ac 3a a4 e9 74 7a 0f 6e 3d 
                    b8 54 af c3 7c 7a 3a dc bd 6a ec 90 5c 59 7a ee 
        AF stripes: 4000
        AF hash:    sha256
        Area offset:290816 [bytes]
        Area length:258048 [bytes]
        Digest ID:  0
Tokens:
Digests:
  0: pbkdf2
        Hash:       sha256
        Iterations: 169125
        Salt:       79 b6 6d 23 e6 38 e3 05 8f f5 22 3b 8a d9 3a 36 
                    40 8b 13 1d c5 74 36 d3 0f 7b 48 6d a6 d1 4c 02 
        Digest:     7d 41 05 a0 22 7d 5d db 6e 8d d8 d0 9e 51 4e c5 
                    11 a0 77 b3 f0 fa 33 48 c1 84 15 cf ed 7b 89 14

Очевидно, что энтропия парольной фразы будет намного ниже энтропии мастер-ключа, поэтому злоумышленник, похитив диск, сможет предпринять попытку взлома методом перебора. Чтобы сделать перебор нецелесообразным, мастер-ключ шифруется не самим паролем, а его ресурсоемким хешем. По умолчанию утилита cryptsetup хеширует пароли столько раз, чтобы для преобразования одной фразы с учетом текущей производительности компьютера требовалось не менее одной секунды, поэтому количество итераций в LUKS называют также стоимостью по времени (time cost). Порядок извлечения мастер-ключа отражен на рисунке 6.

Рисунок 6 — Порядок извлечения мастер-ключа
Рисунок 6 — Порядок извлечения мастер-ключа

Но одной только стоимости хеширования по времени недостаточно, чтобы надежно защитить систему от взлома. Если использовать простые цифровые PIN-коды, то для подбора 6-значного числа потребуется всего 106 попыток, что займет не более 12 дней. Сложность взлома можно увеличить, указав вручную количество итераций с помощью параметра --pbkdf-force-iterations (или определив желаемую стоимость хеширования по времени в количестве миллисекунд с помощью параметра --iter-time, чтобы утилита cryptsetup сама рассчитала необходимое количество итераций). Но даже если поднять стоимость в 100 раз, использование цифровых PIN-кодов все равно не станет безопасным, а пользовательский опыт безнадежно ухудшится (вряд ли пользователь скажет нам спасибо, если загрузка компьютера станет дольше на две минуты).

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

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