О LUKS

2016-08-07

Захотел я зашифровать домашний раздел. Потому что это ноутбук. И на нём накопилось слишком много слишком чужих секретов, ибо использую я его и для работы тоже.

Способов шифрования обнаружилось множество. Во-первых, можно шифровать на уровне файловой системы. Это eCryptfs, EncFS и некоторые другие. Первую использует Ubuntu, когда вы выбираете шифрование домашнего каталога. Так как это всё работает в файловой системе, каждый файл шифруется отдельно, а его имя обфусцируется. В результате само дерево каталогов и файлов остаётся доступным для наблюдения. Атрибуты файлов, вроде размера, даты создания и владельца, тоже вполне себе открыты. Я счёл данный способ неспортивным.

Во-вторых, можно шифровать на уровне блочного устройства. В данном случае зашифровать целый раздел, который монтируется в /home. Тут тоже куча вариантов. И скандально известный TrueCrypt. И давным-давно входящий в Ядро cryptoloop. И тоже весьма ядерный dm-crypt. И то, что называют LUKS — Linux Unified Key Setup.

Почитав-подумав, я решил остановиться на самом попсовом (и наиболее переносимом) варианте: dm-crypt вместе с LUKS.

LUKS

DM — это device mapper — подсистема Ядра для складывания блочных устройств в матрёшку для получения новых возможностей. Тут и кэширование, и шифрование, и в LVM и Docker это используется. Соответственно, dm-crypt — это шифрование блочных устройств на уровне ядра.

dm-crypt может работать сам по себе. Берёте зашифрованный раздел, указываете ключ и параметры шифрования, получаете некое блочное устройство в /dev/mapper, при чтении из которого получаете расшифрованные данные, а при записи в который данные шифруются и пишутся на диск. Типа прозрачненько.

Вот только утомительно это: где-то указывать параметры шифрования, да и ключ ещё. Поэтому придумали LUKS. Это формат задания этих самых параметров и ключей прямо на зашифрованном разделе, в специальном заголовке в самом начале. Ключ защищён парольной фразой, для которой можно использовать и длинный файл со случайными байтиками, скажем, на флэшке. А самих паролей может быть до восьми штук. И каждым из них можно расшифровать раздел.

Ubuntu умеет создавать dm-crypt/LUKS разделы при установке, в том числе и поверх LVM. Но как зашифровать уже имеющийся раздел? Все источники рекомендуют забэкапиться, залить раздел случайным мусором, чтобы следов старых данных не осталось, создать зашифрованный раздел, и восстановиться из бэкапа. Но бэкапить что-то около 300 гигабайт мне показалось слишком унылым. И я нашёл luksipc — LUKS in place convert.

Дело в том, что в случае cryptoloop или голого dm-crypt можно зашифровать раздел без потери содержимого. Ибо до и после шифрования размеры разделов совпадают с точностью до блока. Обычным dd можно, настроив маппинг, читать кусок с исходного (пока незашифрованного) раздела и писать в криптованное устройство. Блок запишется на то же место, но будет уже зашифрованным. Прогнали весь диск — и всё готово.

Но в случае LUKS в начале раздела добавляется собственно заголовок LUKS. И размер зашифрованного раздела становится немного меньше. Можно немного ужать размер файловой системы заранее и перекопировать-зашифровать блоки сдвинув их относительно этого заголовка. Именно этой магией и занимается luksipc. Я проверил в виртуалочке — работает.

Ещё один момент. У меня два диска: маленький SSD на 24 гигабайта, и HDD на 500 гигабайт. Я настроил кэширование доступа к HDD через SSD с помощью bcache. И теперь к этому пирогу нужно добавить ещё один слой шифрования. Именно с самого верху, чтобы и в кэше SSD валялись уже зашифрованные блоки.

device layers

Итак, шаг нулевой — бэкап. Конечно, всё самое ценное у меня всегда запушено в публичные репозитории. Но, на всякий случай, я заархивировал парочку важных каталогов и скинул их на внешний диск. Как выяснилось позднее, определённой ценностью обладают и те самые файлы и папки с точечкой в начале, которых весьма много в домашнем каталоге. Их я, конечно же, забыл забэкапить.

Ставим утилиты по работе с LUKS, а также PAM модуль, который нам понадобится позднее.

$ sudo apt install cryptsetup
$ sudo apt install libpam-mount

Качаем и собираем luksipc в /root, чтобы он был доступен, когда /home не будет.

$ wget https://github.com/johndoe31415/luksipc/archive/master.zip
$ unzip master.zip
$ cd luksipc-master
$ make

Всё можно сделать прямо в нашей системе, безо всяких загрузок с флэшек. Только нужно отмонтировать /home. Так как в Ubuntu нельзя зайти от рута, проще закомментировать /home в /etc/fstab и перезагрузиться. В гуй без /home зайти не получится, но текстовая консоль, которая по Ctrl+Alt+F1, вполне доступна.

Уменьшаем файловую систему. Нужно освободить где-то мегабайт 10. Это 2560 блоков по 4096 байт. Узнать текущий размер файловой системы в блоках можно с помощью tune2fs. Менять размер — с помощью resize2fs. А перед изменением размера придётся явно прогнать проверку ФС: e2fsck. Мой домашний раздел, который поверх bcache, называется /dev/bcache1.

$ sudo tune2fs -l /dev/bcache1
Block count: 116738046
Block size: 4096
$ expr 116738046 - 2560
116735486
$ sudo e2fsck -f dev/bcache1
$ sudo resize2fs /dev/bcache1 116735486

Теперь можно шифровать. Ещё раз вспоминаем, что всё самое важное забэкапили. Переходим туда, где собирали luksipc, в /root/luksipc-master, и запускаем.

$ sudo ./luksipc -d /dev/bcache1

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

И вот тут у меня что-то пошло не так. Запустил я это дело на ночь. Достоверно знаю, что 60% диска отконвертировались без ошибок. Знаю также, что luksipc завершился с кодом 2. Но грешный драйвер вайфая засрал консоль своими сообщениями, и я не знаю, что написал luksipc перед завершением. На первый взгляд всё было хорошо. LUKS раздел имелся.

luksipc генерирует парольную фразу в файл /root/initial_keyfile.bin. Зададим свой пароль, более удобный для ввода с клавиатуры, используя этот файл как существующий пароль.

$ sudo cryptsetup luksAddKey /dev/bcache1 --key-file=/root/initial_keyfile.bin

Я уже говорил, LUKS позволяет задавать до восьми парольных фраз, любая из которых может расшифровать диск. В LUKS это называется Key Slot. luksipc записал в нулевой слот свой случайный ключ, который бережно сложил в файл. Команда luksAddKey добавила ещё один слот, с нашим ключом-паролем. Для добавления нового ключа нужно ввести один из существующих, в данном случае мы использовали файл. Посмотреть все слоты, соли и хэши ключей можно командой luksDump.

$ sudo cryptsetup luksDump /dev/bcache1

LUKS keys

Конечно же, не нужно возиться с командами монтирования шифрованного раздела (а их там требуется, кроме обычного mount) вручную. Cryptsetup, который есть в Ubuntu, умеет запрашивать пароль шифрованного раздела при загрузке. Для этого нужно добавить строчку в файл /etc/crypttab.

# <target name>  <source device>  <key file>  <options>
home  UUID=b068b669-7c4f-4fc5-bf39-7f4f287fad91  none  luks,checkargs=ext4

target name — это имя mapper девайса, которое появится, когда наш шифрованный раздел будет подключен. source device — это имя или uuid зашифрованного раздела, узнать uuid проще всего с помощью команды blkid. key file — это файл с паролем, none означает, что пароль надо спросить у юзера. Опция luks означает, что, собственно, открываем LUKS, а не какой-нибудь cryptoloop. А опция checkargs=ext4 означает, что система проверит, что после расшифровки там присутствует файловая система ext4, а не абы что. Это чтобы проверить правильность пароля.

Ну и нужно, чтобы наш хоум снова монтировался. Поэтому возвращаем его в /etc/fstab, но меняем имя монтируемого устройства.

# <file system>  <mount point>  <type>  <options>  <dump>  <pass>
# /home
/dev/mapper/home  /home  ext4  defaults  0  2

Обратите внимание, в crypttab мы сказали home, и в fstab получили /dev/mapper/home.

Можно ребутиться. Система при загрузке запросит пароль для устройства home. В Ubuntu это даже будет в графическом режиме.

У меня при загрузке вроде всё пошло гладко, обои загрузились, файлы на месте. Почти все. Некоторые файлы и каталоги почему-то отказывались открываться с требованием проверить файловую систему. Ну ладно. Снова выпиливаем home из fstab и перезагружаемся, чтобы проверить.

$ sudo e2fsck -fy -C 0 /dev/mapper/home

Проверка обнаружила кучу битых inode, общие блоки в разных файлах, и вообще всё плохо. Но, часа за полтора (очень долго проходило разделение-дублирование общих блоков) всё восстановилось. По крайней мере, ФС перешла в стабильное состояние.

Самое время вернуть ФС исходный размер, во весь раздел. Это действие и задумывалось изначально. Его, кстати, можно делать и на смонтированной файловой системе.

$ sudo resize2fs /dev/mapper/home

Что получилось с файлами? Некоторые мелкие файлы исчезли. Многие мелкие файлы получились с мусором внутри. Парочка больших файлов получила мусор в середине, слава богу, архивы содержат контрольные суммы, а торренты умеют себя проверять и докачивать повреждённые кусочки. Больше всего пострадали Git и Hg репозитории и, почему-то в основном в формате XML, файлы конфигурации.

Вот тут я обнаружил интересный аспект поведения ПО. Хорошее ПО должно быть готово к тому, что все его надёжные файлы и данные внезапно превратятся в тыкву. Chrome пережил переезд отлично. Git не смог восстановить целостность репозитория, и я не нашёл средств заставить его всё недостающее или поломанное выкачать с сервера, пришлось заново клонировать. IDEA честно ругалась, что, мол, эти файлы какие-то неправильные XML, я их пересоздам заново, но не пересоздавала. А один плагин рушил её при запуске, потому что один каталог где-то внутрях конфигурационной папки оказался не каталогом, а файлом.

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

fsck -y

Но вернёмся к шифрованию. Надо удалить тот ключ, который создавал luksipc, который всё ещё лежит на виду в /root. Команда luksKillSlot удаляет один из паролей из Key Slot LUKS. При этом нужно ввести один из остающихся паролей, так что удалить случайно все пароли и остаться без возможности расшифровать данные не получится.

$ sudo cryptsetup luksKillSlot /dev/bcache1 0

Теперь нужно зашифровать свап. Это считается правилом хорошего тона, ибо в свап могут оказаться всякие открытые данные, включая сами ключи шифрования. Нужно добавить ещё одну строчку в /etc/crypttab. В качестве зашифрованного устройства указывается раздел со свапом, у меня это раздел на SSD /dev/sdb4. uuid тут указывать нельзя, ибо после этой процедуры свап превратится в мусор, и его uuid куда-то пропадёт. В качестве ключа указывается мегаслучайный /dev/urandom.

# <target name>  <source device>  <key file>  <options>
swap  /dev/sdb4  /dev/urandom  swap

В /etc/fstab нужно тоже внести поправку. Теперь свап будет на mapper устройстве.

# <file system>  <mount point>  <type>  <options>  <dump>  <pass>
# swap
/dev/mapper/swap  none  swap  sw  0  0

При такой конфигурации свап будет шифроваться новым случайным ключом при каждой загрузке. Это мегасекурно. Но это сломает hibernate, когда содержимое ОЗУ записывается в свап, а потом восстанавливается при включении. Я на это забил, потому что у меня свап меньше, чем ОЗУ, и hibernate я никогда не пользуюсь, suspend to RAM вполне себе хватает.

Теперь можно перегружаться. Ввод пароля при загрузке — это, конечно, хорошо. Таким образом можно даже зашифровать корневой раздел, если у вас есть незашифрованный /boot. Но я что-то решил, что бинарные файлы дистрибутива, пароли от вайфая в /etc/NetworkManager/ и контейнеры Docker — не настолько важная штука, чтобы её прям уж скрывать. Так что решил ограничиться только домашним разделом.

А для домашнего раздела есть более интересный способ ввода пароля для расшифровки. Тут нам пригодится модуль pam_mount, который мы установили в самом начале. Это модуль PAM, подключаемый модуль аутентификации, который монтирует указанные файловые системы при логине пользователя. Обычно его используют для монтирования сетевого домашнего каталога на тонких клиентах. Но он также может монтировать и зашифрованные разделы. В этом случае имеется особенность: пароль расшифровки должен совпадать с паролем входа в систему. Так что надо придумывать нетривиальный пароль, в него будет упираться вся стойкость шифрования.

XKCD 936

pam_mount настраивается в файле /etc/security/pam_mount.conf.xml. Нужно добавить тег, описывающий, какой раздел и куда монтировать, и немного поправить параметры разлогинивания.

<volume path="/dev/bcache1" mountpoint="/home" cipher="aes-cbc-essiv:sha256" />
<logout wait="5000" hup="0" term="0" kill="yes" />

Наш зашифрованный раздел монтируется в /home при логине любого пользователя. Раздел указывается как устройство в /dev/, можно ли указать uuid, не знаю. Похоже, что в качестве параметра cipher можно указать что угодно, всё равно актуальные значения будут взяты из заголовка LUKS. Я тут написал aes-cbc-essiv:sha256, как было указано в руководстве, хотя в реальности у меня что-то вроде aes-xts-plain64:sha1, что, вроде как, посекурнее будет.

При логауте мы ждём пять секунд и сильно убиваем процессы, которые зачем-то держат хомяк. Говорят, так надо.

Раз уж монтировать будет pam_mount, то нужно закомментировать наш раздел в /etc/fstab. И добавить опцию noauto в /etc/crypttab, чтобы он не запрашивал пароль при загрузке.

# <target name>  <source device>  <key file>  <options>
home  UUID=b068b669-7c4f-4fc5-bf39-7f4f287fad91  none  luks,checkargs=ext4,noauto

Ну и всё, снова можно перезагружаться. Пароль при загрузке не спросят, и всё будет выглядеть почти как раньше. Только на экране логина не будут видны ваши обои (последнего залогиненого юзера), а будет виден дефолтный фон Ubuntu, потому что хомяка на этот момент ещё нет. Ну и после ввода пароля будет заметная секундная пауза, когда шифрованный диск подрубается.

Это может работать и для нескольких пользователей. Но раз /home у них общий, пароль каждого пользователя нужно добавить в LUKS через luksAddKey, тогда любой сможет расшифровать себе раздел. Понятно, что сильно простой пароль зарубит всю идею на корню. Ну и всякие автоблокировки экрана тоже очень даже не помешают. Аналогично, если понадобится поменять пароль, новый пароль надо будет вручную добавить в LUKS, иначе при следующем логине останетесь без /home. В общем, pam_mount — решение не идеальное, но удобное.

В итоге уже неделю живу с зашифрованным разделом. Периодически нахожу и подчищаю последствия ломки файловой системы. Шифрование AES, и говорят, что его реализация в Ядре может использовать инструкции современных процессоров для его быстрого просчёта. В процессе конвертации luksipc рапортовал о скорости в 50 мегабайт в секунду. Нормально. Субъективно даже стало чуток быстрее, но возможно это последствия того, что через bcache прогнали весь диск. Полёт нормальный.

XKCD 538