Laptop – SSD mit dm-crypt/Luks -Verschlüsselung und Opensuse Leap 15 – VI – Key-Slots, PBKDF2- und MK-Iterationen

Im letzten Beitrag meiner Serie zu Voll-Verschlüsselung eines Laptops und angrenzenden Themen

Laptop – SSD mit dm-crypt/Luks -Verschlüsselung und Opensuse Leap 15 – I – Vorüberlegungen
Laptop – SSD mit dm-crypt/Luks -Verschlüsselung und Opensuse Leap 15 – II – Vorüberlegungen zur Virtualisierung
Laptop – SSD mit dm-crypt/Luks -Verschlüsselung und Opensuse Leap 15 – III – Zugriffs-Layer
Laptop – SSD mit dm-crypt/Luks -Verschlüsselung und Opensuse Leap 15 – IV – Disk-Layout
Laptop – SSD mit dm-crypt/Luks -Verschlüsselung und Opensuse Leap 15 – V – kryptierte Partitionen und Alignment

hatten wir LVM-Volumes angelegt und mit LUKS verschlüsselt. Dabei tauchte das Thema der Anzahl von PBKDF2-Iterationen unter LUKS in zwei Ausprägungen auf:

  • Anzahl der Iterationen pro Key-Slot. Diese Zahl sollte aus Sicherheitsgründen möglichst hoch sein.
  • Anzahl der Iterationen für den Master-Key-Digest (MK-Digest; hash-basierter Fingerprint). Weniger sicherheitsrelevant.

Kann man diese Iterationen individuell steuern oder im Nachhinein irgendwie ändern? Die Antwort ist: Für die sog. Key Slots schon, für den MK-Digest nicht ohne Neu-Anlage des LUKS-Volumes (mit Datensicherung und erneuter Einspielung) und damit Neu-Verschlüsselung oder Neuanlage des LUKS-Headers zum gleichen Master-Key.

Es gibt inzwischen zwar ein Kommando “cryptsetup-reencrypt”, das einen Lauf zur Recryptierung aller vorhandenen Daten in situ durchführt. Die man-Seite bietet aber auch für “cryptsetup-reencrypt” keinen Parameter zur separaten Festlegung der Iterationszahl für den MK-Digest an. Das Kommando gilt zudem als experimentell. Ich selbst habe es noch nie ausprobiert.

Die Iterationsanzahl des MK-Digest kann man beim Anlegen eines LUKS-Devices (indirekt) festlegen. Änderungen erfordern eine Neuanlage oder einen Re-Encrypt-Prozess. Hält man die Anzahl der Iterationen für den Digest für zu klein, sollte man also am besten gleich zu Anfang gegensteuern. Das ist das erste Thema dieses Beitrags.

Das zweite Thema ist, wie man mit Hilfe einer temporären Umschlüsselung die Iterationsanzahl für einen “Key Slot” ändert. Dabei sehen wir nebenbei auch, wie man einen weiteren “Key Slot” anlegt.

Das dritte Thema ist ein Backup des LUKS-Headers.

Zwischenzeitlich hat mich eine Mail erreicht, in der sich ein Leser vor einer weiteren Durchführung von LUKS-Befehlen eine kurze, prägnante Erläuterung von LUKS-Prinzipien und der sog. Key-Slots wünschte. Die nachfolgenden Schritte erfordern tatsächlich ein klares Verständnis des LUKS-Verfahrens. Ich stelle daher eine knappe Zusammenfassung von LUKS an den Anfang.

Zusammenfassung grundlegender LUKS-Elemente

LUKS setzt zur Verschlüsselung von Daten ein vorgebbares symmetrisches Krypto-Verfahren (wie z.B. aes-xts-plain64) ein. Der zugehörige Schlüssel – der Master-Key [MK] – wird bei Anlage eines LUKS-Volumens automatisch über einen Zufallszahlengenerator in vordefinierter Länge (Key Size) festgelegt. Der MK wird ggf. mehrfach in verschlüsselter und gesplitteter Form im System hinterlegt [KMK = kryptierter MK]. Benötigte Schlüssel – ich
nenne sie nachfolgend PB-Keys – zur Ver- und Entschlüsselung des MK werden dagegen nicht im System hinterlegt; ein PB-Key wird bei einem User-Zugang vielmehr auf Basis eines eingegebenen Passworts berechnet (s.u). Zum Master-Key wird mit Hilfe des sog. PBKDF2-Verfahrens (salted “password based Key derivation function”; s. die Links unten) über eine definierten Anzahl von Hash/HMAC-Iterationen) genau ein gültiger sog. “MK-Digest” als eindeutige und kennzeichnende Prüfsumme berechnet und hinterlegt. Die Anzahl der Iterationen für den Digest nenne ich nachfolgend MKD-Iterationen.

LUKS erlaubt bis zu 8 verschiedene Passphrases/Passwörter für den User-Zugang zu einem verschlüsselten Volume. Diese bis zu 8 Passwörter definieren sog. “Key-Slots” [0-7]: Für jedes definierte Passwort wird wiederum mit dem PBKDF2/HMAC-Verfahren und einer vordefinierten Anzahl von Iterationen [PBK-Iterationen] eines Hash-Verfahrens ein PB-Key] berechnet. Jeder Key-Slot verwahrt eine eigene verschlüsselte Variante des MK-Keys [KMK], die durch (symmetrische) Verschlüsselung mit dem slot-spezifischen PB-Key erzeugt wurde. Die Anzahl der PK-Iterationen kann slot-spezifisch definiert werden und wird pro Slot hinterlegt.

Im Rahmen des User-Zugangs zu den Inhalten eines LUKS-Volumes (per “cryptsetup open”) wird auf Basis einer eingegebenen Passphrase zunächst ein PB-Key berechnet. Dann werden mit diesem Key durch Entschlüsselung der KMK-Information pro Key-Slot ein Master-Key-Kandidat [MKK] und über PBKDF2 ein zugehöriger MKK-Digest ermittelt. Die MKK-Digests werden dann mit dem hinterlegten MK-Digest verglichen, um den richtigen Master-Key aus den MKKs auszuwählen und für anschließende Daten-Ent/Ver-Schlüsselungsprozesse im RAM hinterlegt. Der MK wird natürlich nur dann gefunden, wenn die Passphrase mindestens für einen der aktiven Key-Slots korrekt war.

Alle zur Verschlüsselung notwendigen Daten (Hash-Verfahren, Salts, MK-Digest, Anzahl MKD-Iterationen, Slots inkl. zugehöriger Salts, PK-Iterationen und kryptiertem MK) werden im sog. LUKS-Header eines verschlüsselten Volumes aufbewahrt. Der PB-Key wird jedoch nie (!) hinterlegt. Siehe für weitere Infos und Details etwa
https://gitlab.com/cryptsetup/cryptsetup/blob/master/docs/on-disk-format.pdf
dm-crypt/Luks – Begriffe, Funktionsweise und die Rolle des Hash-Verfahrens – I
dm-crypt/Luks – Begriffe, Funktionsweise und die Rolle des Hash-Verfahrens – II

Erhöhung der Iterationen für den MK-Digest und Zeitbedarf beim Booten

Themen wie Kollisionsfreiheit, hinreichende Entropie und die Zahl der PBKDF2/HMAC-Iterationen sind für den passwort-basierten Zugang zum MK sehr viel wichtiger als für den MK-Digest. Man versteht dies am besten bzgl. der Kollisionsfreiheit: Die Erzeugung desselben PB-Keys mit einer an sich falschen Passphrase erlaubt den Zugang zum LUKS-Volume; eine Kollision beim Erzeugen des Digest aus einem falschen MK-Wert erlaubt dagegen noch keine Entschlüsselung der LUKS-Daten.

Off Topic: Ich möchte am Rande betonen, dass das Thema einer hinreichenden Entropie für die Festlegung des MK-Keys selbst sehr wohl von entscheidender Bedeutung für die Sicherheit des gesamten LUKS-Verfahrens ist. Das ist aber ein anderes Thema, bei dem man sich mit der generellen Sicherheit von (u)random-basierten Zufallszahlengeneratoren (PRNGs) für 512-Bit-Strings unter Linux befassen müsste. Hier habe ich ein gewisses Gottvertrauen in die Linux-Kernel- und -Security-Fachleute :-). Siehe auch die Links am Ende des Artikels, im Besonderen den letzten Link für
eine detaillierte Diskussion.

Trotz des oben diskutierten Arguments mag der eine oder andere Leser ggf. mit der relativ geringen Iterationszahl zum MK-Digest nicht zufrieden sein. Im letzten Beitrag hatten wir für 300.000 Iterationen bei der PB-Key-Berechnung [PBK-Iterationen] ja nur 18.750 Iterationen für den MK-Digest erhalten [MKD-Iterationen]. Typischerweise kommt da ein Faktor 16 zum Tragen. Nun könnten wir die Anzahl der Digest-Iterationen indirekt durch eine größere Vorgabe der Iterationen (z.B. 2.400.000) zur Key-Berechnung substanziell auf 150.000 erhöhen. Das hat aber potentiell Auswirkungen auf den Zeitbedarf beim Booten. Warum?

Grub 2 ist sehr langsam bzgl. der Ausführung von PBKDF2-Iterationen. Siehe hierzu
Full encryption with LUKS (SHA512, aes-xts-plain64) – grub2 really slow

Und wenn man mehrere Key-Slots hat, so ist pro aktivem Key-Slot ein MK-Test-Digest zu berechnen, um zu ermitteln, welcher der ermittelten Master-Key-Kandidaten denn korrekt ist. Hat man mehrere Slots, so wird die Zeit für die Berechnung der MKK-Digests für den Vergleich mit dem MK-Digest linear vervielfacht. Benötigt Grub2 etwa 4 Sek für n PBKDF2-Key-Iterationen und wählt man die Zahl der Digest-Iterationen etwa 1/4 so groß wie die der normalen Key-Iterationen, so würde sich bei insgesamt 4 Slots die benötigte Zeit gerade auf 8 Sekunden verdoppeln. Das Ganze dann ggf. mal der verschlüsselten Volumes, die von GRUB direkt entschlüsselt werden müssen.

Wer also Wert auf einen schnellen Boot-Vorgang legt, sollte wenige Key-Slots und eine rel. geringe Zahl von MKD-Iterationen vorsehen. Also bitte nicht blind aus falschen Sicherheitserwägungen heraus beliebig hohe Iterationszahlen für den MK-Digest einstellen!

Festlegung der MKD-Iterationen

Ich zeige am Beispiel des im letzten Artikel besprochenen LVM Volumes “/dev/vga/lva1” mal, wie man bei der Anlage eine höhere Iterationszahl (40.000) für den MK-Digest provoziert und die Iterationszahl des automatisch erzeugten Key-Slots “0” anschließend wieder auf 300.000 reduziert.

Tests zeigen, dass die Anzahl der MKD-Iterationen bei aktuellen Versionen um einen Faktor 16 kleiner als die Anzahl der normalen PBK-Iterationen gewählt wird. Letztere werden durch den Parameter “–pbkdf-force-iterations” festgelegt. Leider gibt es meines Wissens bislang keinen separaten Parameter für cryptsetup oder cryptsetup-reencrypt zur direkten Festlegung der MKD-Iterationen.

Also müssen wir das Volume mit folgendem Befehl verschlüsseln:

cryptsetup –cipher aes-xts-plain64 –key-size 512 –hash sha512 –pbkdf-force-iterations 640000 –align-payload=2048 -v luksFormat /dev/vga/lva1

Warnung: Das muss schon anfänglich geschehen. Ansonsten ist “cryptsetup luksFormat ” bzgl vorhandener Daten ein destruktiver Befehl. Hat man also bereits Daten im verschlüsselten Volume, so sind diese zunächst unbedingt zu sichern und nach der Neuverschlüsselung wieder einzuspielen.

Da ich das Volume im letzten Beitrag bereits angelegt, aber noch keine Daten hinterlegt hatte, entferne ich es erst auf LVM-Ebene, lege es dann wieder an und erzeuge dann einen neuen LUKS Header mit dem geänderten Parameter:

mytux:~ # lvremove /dev/vga/lva1       
Do you really want to remove and DISCARD active logical volume vga/lva1? [y/n]: y
  Logical volume "lva1" successfully removed
mytux:~ # lvcreate -n lva1 -L80G vga
  Logical volume "lva1" created.
mytux:~ # cryptsetup --cipher aes-xts-plain64 --key-size 512 --hash sha512 --pbkdf-force-iterations 640000  --align-payload=2048 -v luksFormat /dev/vga/
lva1 

WARNING!
========
This will overwrite data on /dev/vga/lva1 irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase for /dev/vga/lva1: 
Verify passphrase: 
Command successful.
  
mytux:~ # cryptsetup luksDump /dev/vga/lva1
LUKS header information for /dev/vga/lva1

Version:        1
Cipher name:    aes
Cipher mode:    xts-plain64
Hash spec:      sha512
Payload offset: 4096
MK bits:        512
MK digest:      a4 .. .. .. 
MK salt:        ..
                ..
MK iterations:  40000
UUID:           e5a1bc1e-8071-3c74-ab26-17335521b816 

Key Slot 0: ENABLED
        Iterations:             640000
        Salt:                   ..
                                ..
        Key material offset:    8
        AF stripes:             4000
Key Slot 1: DISABLED
...

(Die neue UUID notieren wir uns für die kommende Opensuse Leap-Installation!).

Genau wie beabsichtigt! Siehe die Ausgabe für die “MK iterations”. Nun ist aber auch die Anzahl der (PK-) Iterationen für Key Slot 0 auf 640.000 angewachsen. Wie können wir diese Zahl wieder reduzieren?

Änderung der PBKDF2-Iterationen für einen definierten Key-Slot

In unserem Fall liegt genau ein Key-Slot “0” vor. Aus der oben beschrieben Art, wie LUKS arbeitet, ergibt sich, dass man den Key Slot neu anlegen muss. Dann brauchen wir aber einen temporären Slot, um überhaupt noch Zugang zum Master-Key zu bekommen. Für das Anlegen eines weiteren Key-Slot gibt es den Befehl “cryptsetup luksAddKey“. Die zugehörigen man-Seiten erläutern die möglichen Parameter. Wir bringen folgenden Befehl zum Einsatz:

cryptsetup –pbkdf-force-iterations 300000 –key-slot 1 luksAddKey /dev/vga/lva1

Die Passphrase und die Anzahl der Iteration müssen zwar nicht mit den gewünschten Daten übereinstimmen; wir werden den Slot 1 anschließend sowieso wieder löschen. Dennoch sollten auch hier hohe Iterationszahlen und vernünftige Passwörter gewählt werden. Denn wer weiß, welche Reste beim Löschen auf der Disk verbleiben, aus denen ein Hacker potentiell was machen kann.

mytux:~ # cryptsetup --pbkdf-force-iterations 300000 --key-slot 1 luksAddKey /dev/vga/lva1
Enter any existing passphrase: 
Enter new passphrase for key slot: 
Verify passphrase: 
mytux:~ # cryptsetup luksDump /dev/vga/lva1
LUKS header information for /dev/vga/lva1

Version:        1
Cipher name:    aes
Cipher mode:    xts-plain64
Hash spec:      sha512
Payload offset: 4096
MK bits:        512
MK digest:      ..
MK salt:        ..
                ..
MK iterations:  40000
UUID:           e5a1bc1e-8071-3c74-ab26-17335521b816

Key Slot 0: ENABLED
        Iterations:             640000
        Salt:                   ..
                                ..
        Key material offset:    8
        AF stripes:             4000
Key Slot 1: ENABLED
        Iterations:             300000
        Salt:                   ..
                                ..
        Key material offset:    512
        AF stripes:             4000
Key Slot 2: DISABLED
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED
mytux:~ # 

Hinweise: Die Slot-Nummer wird immer nach dem Device angegeben. Das Hash-Verfahren (hier SHA-512) für das PBKDF2-Verfahren wird aus dem vorhandenen LUKS-Header übernommen.

Mit Hilfe des neuen Slots können wir nun den alten Slot mit “cryptsetup luksKillSlot” löschen. Danach legen wir ihn neu an; dabei müssen wir dann das gewünschte Passwort und die gewünschte Iterationszahl (hier: 300.000) korrekt angeben. Also:

mytux:~ # cryptsetup luksKillSlot /dev/vga/lva1  0
Enter any remaining passphrase: 
mytux:~ # cryptsetup --pbkdf-force-iterations 300000 --key-slot 0 luksAddKey /dev/vga/lva1
Enter any existing passphrase: 
Enter new passphrase for key slot: 
Verify passphrase: 
mytux:~ # cryptsetup luksKillSlot /dev/vga/lva1  1
Enter any remaining passphrase: 
mytux:~ # cryptsetup luksDump /dev/vga/lva1
LUKS header information for /dev/vga/lva1

Version:        1
Cipher name:    aes
Cipher mode:    xts-plain64
Hash spec:      sha512
Payload offset: 4096
MK bits:        512
MK digest:      ..
MK salt:        ..
                ..
MK iterations:  40000
UUID:           e5a1bc1e-8071-3c74-ab26-17335521b816

Key Slot 0: ENABLED
        Iterations:             300000
        Salt:                   ..
                                ..
        Key material offset:    8
        AF stripes:             4000
Key Slot 1: DISABLED
Key Slot 2: DISABLED
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED
mytux:~ # 

Wie gewünscht!
Das gleiche Spielchen können wir nun natürlich auch für das im letzten Artikel angelegte LVM volume “/dev/vgs/lvs1” für den verschlüsselten künftigen Swap-Bereich durchführen.

Hinweis: Eine andere, aus meiner Sicht deutlich gefährlichere Weise, LUKS-Parameter zu ändern, führt über ein Auslesen des MK und eine Neuanlage des LUKS-Headers. Siehe hierzu die zweite Antwort in:
https://unix.stackexchange.com/questions/101343/how-to-change-the-hash-spec-and-iter-time-of-an-existing-dm-crypt-luks-device

Da wir bei unserem Vorgehen unsere LVM-Volumes zwischenzeitlich gelöscht hatten, müssen wir nun natürlich auch wieder eine Formatierung des geplanten Swap-Bereiches und des Volumes für das root-Filesystem durchführen. Das hatte ich bereits im letzten Artikel beschrieben. Ändert man aber in der Zukunft nur noch die PBK-Iterationen für die Key-Slots ab, so ist das natürlich nicht notwendig.

Backup des LUKS-Headers

Ein wichtiger Schritt, den man nach jedem Anlegen eines LUKS-Volumes durchführen sollte, ist die Sicherung des LUKS-Headers. Bastelt man – wie wir – am Partitions- und LVM-Layout noch herum, ist die Gefahr, Teile des LUKS-Headers zu beschädigen, groß. Der Header enthält alle wichtigen Informationen zur Kryptierung – u.a. auch den MK in verschlüsselter Form. Von großer Bedeutung sind im Besonderen aber auch die diversen SALTs, die in die PBKDF2-Verfahren eingehen. Siehe zur Thematik auch die unten angegebenen Links.

Man kann den Header selbst oder auch den MK-Key im Notfall aus dem Backup rekonstruieren.

Muss man das Header Backup besonders verwahren? Nun ja, genauso, wie man seinen Laptop oder sein verschlüsseltes Device selbst gegen physikalischen Zugriff schützen möchte. Es ist klar, dass jeder, der das Device physikalisch kontrolliert, den LUKS Header auslesen kann. Dennoch hat er dadurch keinen direkten Zugang zu den Daten. Welchen Vorteil hätte er dadurch potentiell?
Nun, er würde die Salts und Iterationszahlen kennen. Das beschleunigt Brute Force Attacken gegenüber einer allgemeingültigen Vorbereitung von Rainbow Tabellen. Aber er muss immer noch gegen die PBKDF2-Iterationen, das Hinterlegen des MKs in 4000 separaten Stripes und hoffentlich gute Passphrases ankämpfen. Genau dafür ist LUKS ja aber gemacht! Derjenige, der deinen Laptop gestohlen hat und damit den Header kennt, soll trotzdem keine Chance haben. Dennoch sollte man den Header natürlich nicht öffentlich zugänglich herumliegen lassen. Ein guter Ort ist etwa ein Veracrypt-Volume, in den man ihn direkt befördert.

Zur Sicherung des Headers verwenden wir den Befehl

cryptsetup luksHeaderBackup /dev/vga/lva1 –header-backup-file /PFAD_ZU_Veracrypt_VOLUME/NAME_DER_BACKUP_DATEI

Der Vollständigkeit halber sei abschließend auch angegeben, wie man den MK aus dem Header extrahiert:
Dazu muss das LUKS-Device zunächst wie oben beschrieben mit “cryptsetup open” geöffnet werden. Danach setzt man das Kommando “dmsetup table –showkeys” durch; den Schlüssel identifiziert man in der evtl. angegebenen Liste über den Namen des geöffneten Devices unter “/dev/mapper”. Den Schlüssel kann man sich dann natürlich auch abspeichern. Das möge jeder mal selber ausführen. Aber Vorsicht! Macht ihr das für eine bislang externe Disk auf einem unverschlüsselten System, so seid euch bitte der möglichen Gefahren bewusst!

Fazit und Ausblick

Wir haben gesehen, wie wir die Anzahl der diversen durchzuführenden PBKDF2-Iterationen unter LUKS beeinflussen können. Während die MKD-Iteratioen möglichst schon bei der Anlage eines LUKS-Headers geändert werden sollten, können die PBK-Iterationen jederzeit leicht ohne Gefahr eines Datenverlusts abgeändert werden. Das Backup des LUKS-Headers eines jeden LUKS-Volumes ist eine einfache Pflichtübung. Bei der Gelegenheit haben wir auch ein wenig den Umgang mit verschiedenen Varianten des “cryptsetup”-Befehls geübt.

Wir haben zudem alle Voraussetzungen geschaffen, um endlich Opensuse Leap 15 auf unserer SSD zu installieren. Das ist Gegenstand des nächsten Artikels.

Links

Key-Slots, Key- und Iterations-Manipulation unter LUKS
https://unix.stackexchange.com/questions/101343/how-to-change-the-hash-spec-and-iter-time-of-an-existing-dm-crypt-luks-device
https://unix.stackexchange.com/questions/101398/why-does-luks-need-to-generate-hash-values
https://www.thegeekstuff.com/2016/03/cryptsetup-lukskey/

Symmetrische XTS-Verschlüsselung
https://security.stackexchange.com/questions/101995/explanation-of-the-xts-encryption-mode

Sicherheit der MK-Generierung und des LUKS-Verfahrens
https://askubuntu.com/questions/97196/how-secure-is-an-encrypted-luks-filesystem
https://crypto.stackexchange.com/questions/20171/what-are-the-security-benefits-of-luks
https://discreete-linux.org/analysis/cryptsetup_1.4.1-luks-analysis-en.pdf

LUKS Header Backup
http://julien.coubronne.net/2017/02/03/luks-backups-and-headers/
https://www.lisenet.com/2013/luks-add-keys-backup-and-restore-volume-header/
https://blog.tinned-software.net/create-a-luks-encrypted-partition-on-linux-mint/