Verschluesselter USB-Speicherstick mit LUKS HOWTO
Version 1.1, 2006-07-30, Veit Wahlich <cru at zodia dot de>
Dieses How-To soll erklaeren, wie man mit Hilfe der LUKS-Extensions fuer cryptsetup hochsicher verschluesselte Wechselspeicher erstellt. Zudem enthaelt es wissenswerte Grundlagen zu aktuellen symmetrischen Verschluesselungsverfahren, deren Modi sowie Erweiterungen fuer Datentraegerverschluesselung.
Voraussetzungen
Um die in diesem How-To beschriebenen Taetigkeiten vollstaendig durchfuehren zu koennen, wird benoetigt:
- ein USB-Speicherstick/-Festplatte oder anderer Wechselspeicher
- fdisk aus util-linux
- cryptsetup-luks (impliziert device mapper und dm-crypt)
- mke2fs aus den e2fsprogs
- ggf. mkfs.vfat aus den dosfstools
- randfs
- fuer volle Desktop-Integration (GNOME, vermutlich auch KDE und andere):
- cryptsetup-luks 1.0.1 oder hoeher
- D-Bus 0.60 oder hoeher
- HAL 0.5.7 oder hoeher
Vorbereitung
Zunaechst muessen wir genau wissen, welcher Device Node unserem
USB-Stick zugewiesen wird, damit wir nicht versehentlich die
falsche Partition loeschen/ueberschreiben.
Die meiner Meinung nach sicherste Methode dies herauszufinden ist,
zunaechst mit dmesg
das Kernel-Log zu loeschen.
# dmesg -c ...
Nun wird der USB-Wechselspeicher angeschlossen und wir koennen
mit dmesg
die Meldungen des Kernels dazu ausgeben
lassen.
Dabei sollten wir nach dem Anstecken des Geraets noch einige
Sekunden warten, bis das Geraet sicher erkannt ist.
# dmesg usb 1-2: new high speed USB device using ehci_hcd and address 3 usb 1-2: configuration #1 chosen from 1 choice scsi20 : SCSI emulation for USB Mass Storage devices usb-storage: device found at 3 usb-storage: waiting for device to settle before scanning Vendor: SanDisk Model: Cruzer Mini Rev: 0.1 Type: Direct-Access ANSI SCSI revision: 02 SCSI device sda: 1000944 512-byte hdwr sectors (512 MB) sda: Write Protect is off sda: Mode Sense: 03 00 00 00 sda: assuming drive cache: write through sda: sda1 sd 20:0:0:0: Attached scsi removable disk sda sd 20:0:0:0: Attached scsi generic sg0 type 0 usb-storage: device scan complete
In meinem Fall ist dem USB-Stick also der Block Device Node
/dev/sda
zugewiesen, auf dem sich die primaere
Partition /dev/sda1
befindet.
Da mein GNOME-Desktop so konfiguriert ist, dass er Wechselmedien
automatisch einbindet, ueberpruefe ich mit mount
,
ob und wohin die bereits vorhandene Partition gemountet wurde.
# mount ... /dev/sda1 on /media/disk type vfat (rw,noexec,nosuid,nodev,shortname= winnt,uid=500)
In diesem Fall muss ich die Partition also zunaechst aushaengen, damit ich auf dem Wechselspeicher frei arbeiten kann.
# umount /media/disk
Optional: Sichern des USB-Sticks
Da mein USB-Stick von Werk aus Software fuer das andere Betriebssystem enthaelt, mache ich ein vollstaendiges Backup in Form eines Images des gesamten Block-Geraets. Dieser Schritt ist natuerlich optional!
# dd if=/dev/sda of=usbstick-backup.img 1000944+0 Datensätze ein 1000944+0 Datensätze aus 512483328 Bytes (512 MB) kopiert, 21,7406 Sekunden, 23,6 MB/s
Das Backup-Image liesse sich spaeter durch einfaches Vertauschen
von if und of auf den Stick zurueckspielen.
Da das Image ziemlich gross ist, aber nur wenige Daten enthaelt,
kann man es noch sehr gut mit gzip
oder
bzip2
komprimieren, um viel Platz zu sparen.
Partitionierung
Nun sehen wir uns die Partitionstabelle naeher an.
# fdisk -l /dev/sda Disk /dev/sda: 512 MB, 512483328 bytes 16 heads, 63 sectors/track, 993 cylinders Units = cylinders of 1008 * 512 = 516096 bytes Device Boot Start End Blocks Id System /dev/sda1 1 993 500355+ 6 FAT16
Der Wechselspeicher enthaelt in meinem Fall also eine 512MB grosse FAT-Partition. Ich habe mich dazu entschlossen, dass ich zwei getrennte Partitionen anlegen moechte; eine verschluesselte Partition fuer private Daten und eine unverschluesselte FAT-Partition fuer den Fall, dass ich einmal Daten mit einem Benutzer des anderen Betriebssystems austauschen moechte.
Ich loesche also zunaechst die vorhandene Partition (sda1) und lege
anschliessend zwei neue primaere Partitionen an; sda1 als Container
fuer ein LUKS-verschluesselte Linux-Dateisystem und sda2 als
FAT-Partition.
Fuer letztere setze ich schliesslich noch den Partitionstyp (0x06)
und schreibe mit w
die Partitionstabelle auf den
Wechselspeicher.
# fdisk /dev/sda Command (m for help): d Selected partition 1 Command (m for help): n Command action e extended p primary partition (1-4) p Partition number (1-4): 1 First cylinder (1-993, default 1): Using default value 1 Last cylinder or +size or +sizeM or +sizeK (1-993, default 993): 496 Command (m for help): n Command action e extended p primary partition (1-4) p Partition number (1-4): 2 First cylinder (497-993, default 497): Using default value 497 Last cylinder or +size or +sizeM or +sizeK (497-993, default 993): Using default value 993 Command (m for help): t Partition number (1-4): 2 Hex code (type L to list codes): 6 Changed system type of partition 1 to 6 (FAT16) Command (m for help): p Disk /dev/sda: 512 MB, 512483328 bytes 16 heads, 63 sectors/track, 993 cylinders Units = cylinders of 1008 * 512 = 516096 bytes Device Boot Start End Blocks Id System /dev/sda1 1 496 249952+ 83 Linux /dev/sda2 497 993 250488 6 FAT16 Command (m for help): w The partition table has been altered! Calling ioctl() to re-read partition table. WARNING: If you have created or modified any DOS 6.x partitions, please see the fdisk manual page for additional information. Syncing disks.
Wer keine Partition fuer den Austausch mit Systemen unter dem anderen Betriebssystem benoetigt, laesst die Partition einfach weg und legt statt dessen einfach die erste Partition ueber das komplette Geraet an.
Optional: FAT-Partition formatieren
Wer im vorhergehenden Schritt eine FAT-Partition angelegt hat, kann diese nun formatieren.
# mkfs.vfat -n "unsicher" /dev/sda2 mkfs.vfat 2.11 (12 Mar 2005)
Der Parameter -n gibt dabei ein FAT-Label an. Ich halte das hier fuer wichtig, da mein Stick ja aus einem sicheren (verschluesslten) und einem unsicheren (unverschluesselten) Teil bestehen wird, und ich moechte verhindern, dass Daten auf der falschen Partition gespeichert werden. FAT-Labels koennen uebrigens max. 11 Zeichen lang sein.
Auswahl des Cipher Spec
In meinem Beispiel werde ich den Cipher Spec
aes-cbc-essiv:sha256
mit 256bit Schluessellaenge
verwenden.
Dabei steht aes
fuer den Advanced Encryption Standard,
den international anerkannten Rijndael-Algorithmus.
AES wird von quasi allen Distributionskerneln unterstuetzt und
bietet maximale Sicherheit.
Allerdings muss darauf hingewiesen werden, dass AES (fuer synchrone
Verschluesselungsverfahren recht ungewoehnlich) beim Lesen
langsamer arbeitet als beim Schreiben und auch allgemein nicht sehr
performant ist.
Als Alternative bietet sich die Verwendung des schnelleren
Algorithmus twofish
an.
Bei Twofish handelt es sich um eine sequenzielle Verkettung des
bekannten Blowfish-Algorithmus.
Twofish war neben Rijndael bei der Wahl zum Advanced Encryption
Standard der zweite Finalist, bei dem ebenfalls keine
kryptographischen Schwaechen gefunden und keine Bedenken geaeussert
werden konnten.
Das zweite Element ist der Modus der symmetrischen
Verschluesselung, cbc
steht dabei fuer Cipher Block
Chaining.
Wird ein Klartextblock verschluesselt, kann neben dem Schluessel
auch ein Initialisierungs-Vektor (IV) angegeben werden, fehlt
dieser, geht der Algorithmus von 0 aus.
Im CBC-Modus wird dabei das resultierende Chiffrat eines Blocks
als IV fuer den naechsten Block verwendet.
Dadurch, dass sich jeder Block (mit Ausnahme des ersten Blocks)
immer nur durch Kenntnis des vorhergehenden Blocks entschluesseln
laesst, wird die Sicherheit des gesamten Chiffrats deutlich
erhoeht und vor allem sog. Dictionary Attacks, also Versuche den
Schluessel durch Kenntnis regelmaessiger oder bekannter Strukturen
zu errechnen bzw. zu erraten, wirksam erschwert.
Dieser Modus ist besonders fuer Datentraeger-/Dateisystemverschluesselung
geeignet, da diese i.d.R. aus immer gleich grossen Sektoren/Bloecken
aufgebaut sind, welche stets am Stueck gelesen (und entschluesselt)
bzw. geschrieben (verschluesselt) werden, sodass beim Aufteilen des
Datentraegerblocks in (kleienre) Chiffratbloecke durch die
Verkettung kein nennenswert groesserer Rechenaufwand entsteht.
Als letzten Parameter traegt der Cipher Spec den
Sektorverkettungsmodus.
Hier verwende ich mit essiv:sha256
den ESSIV-Modus
mit SHA256-Hash (256bit Secure Hashing Algorithm).
ESSIV ist dabei aehnlich dem CBC, speziell fuer Datentraeger:
Waehrend normalerweise (im Modus plain
) immer wieder
der Standard-IV verwendet wird, wenn ein neuer Sektor/Block
ver-/entschluesselt werden soll, macht ESSIV den IV eines jeden
Sektors/Dateisystemblocks von seiner Sektor-/Blocknummer abhaengig.
Dabei wird ein aus dem Schluessel sowie der Sektornummer
errechneter Hashwert (der in seiner Laenge identisch mit der
Groesse des IVs sein sollte, die wiederum meist der
Schluessellaenge entspricht) als Initialisierungs-Vektor des
ersten Chiffratblocks innerhalb des Sektors/Blocks verwendet.
Diese Technik soll neben der allgemeinen Erhoehung der Sicherheit
gespeicherter Daten auch speziell Angriffe auf wiederkehrende
Strukturen in Dateisystemen verhindern bzw. erschweren, z.B.
Dateizuordnungstabellen und deren Kopien.
Anlegen des LUKS-Containers
Auf unserer neuen Linux-Partition legen wir nun einen neuen LUKS-Container mit dem zuvor besprochenen Cipher Spec an. Dabei legen wir auch das Master-Passwort des Containers fest.
# cryptsetup -c aes-cbc-essiv:sha256 -s 256 luksFormat /dev/sda1 WARNING! ======== Daten auf /dev/sda1 werden unwiderruflich überschrieben. Are you sure? (Type uppercase yes): YES Enter LUKS passphrase: Verify passphrase: Command successful.
Ein LUKS-Container kann ueber bis zu 8 Passwoerter verfuegen. Dies ermoeglicht sowohl die Nutzung des Datentraegers durch mehrere Benutzer mit je eigenem Passwort, als auch das Festlegen eines Not-Passworts:
# cryptsetup luksAddKey /dev/sda1 Enter any LUKS passphrase: Verify passphrase: key slot 0 unlocked. Enter new passphrase for key slot: Verify passphrase: Command successful.
Aber auch wenn nur ein Passwort verwendet werden soll, empfiehlt es sich, fuer dieses Passwort einen zweiten der acht Slots zu belegen: Sollte sich in einem der Slots ein Sektorfehler einschleichen oder die Daten dort auf andere Weise beschaedigt werden, kann der Container so trotzdem noch anhand des zweiten Slots entsperrt werden.
Oeffnen des Containers
Sobald der Container angelegt ist, kann er ueber den Device Mapper
geoeffnet werden, ich habe hier als Namen fuer das Device Mapping
foobar
verwendet.
Der Name ist natuerlich frei waehlbar.
# cryptsetup luksOpen /dev/sda1 foobar Enter LUKS passphrase: key slot 1 unlocked. Command successful.
Wie man sieht, habe ich hier das Passwort von Slot 1 benutzt (statt dem Master-Passwort in Slot 0).
Anlegen des Dateisystems im Container
Ist der Container erfolgreich geoeffnet, kann in ihm ein Dateisystem angelegt werden.
Ich habe mich fuer Ext3 entschieden. Ueber den Sinn oder Unsinn von Journaling Filesystems auf Wechseldatentraegern kann man sich streiten, ich persoenlich investiere aber gern ein paar MByte fuer das Journal und riskiere eine schnellere Alterung des Flash-Speichers zu Gunsten schneller, atomarer Korrektur von Dateisystemfehlern, falls der Stick mal versehentlich ohne Abschluss abgezogen wurde.
Wer lieber Ext2 (kein Journal) statt Ext3 verwenden moechte, laesst
die Option -j
weg. Der Parameter -m 0
sorgt dafuer, dass kein Speicher auf dem Geraet fuer die alleinige
Nutzung durch den Superuser (root) reserviert bleibt.
Schliesslich verwende ich noch -L
, um dem Dateisystem
ein Label zu geben.
Ext2-/Ext3-Labels duerfen uebrigens bis zu 16 Zeichen lang sein.
# mke2fs -j -L "sehr sicher" -m 0 /dev/mapper/foobar mke2fs 1.38 (30-Jun-2005) Dateisystem-Label=sehr sicher OS-Typ: Linux Blockgröße=1024 (log=0) Fragmentgröße=1024 (log=0) 62496 Inodes, 249460 Blöcke 0 Blöcke (0.00%) reserviert für den Superuser erster Datenblock=1 Maximum filesystem blocks=67371008 31 Blockgruppen 8192 Blöcke pro Gruppe, 8192 Fragmente pro Gruppe 2016 Inodes pro Gruppe Superblock-Sicherungskopien gespeichert in den Blöcken: 8193, 24577, 40961, 57345, 73729, 204801, 221185 Schreibe Inode-Tabellen: erledigt Erstelle Journal (4096 Blöcke): erledigt Schreibe Superblöcke und Dateisystem-Accountinginformationen: erledigt Das Dateisystem wird automatisch alle 25 Mounts bzw. alle 180 Tage überpr üft, je nachdem, was zuerst eintritt. Veränderbar mit tune2sf-c oder -t .
Da jetzt ein Dateisystem auf dem Wechselmedium existiert, kann
dieses mit mount
in das Dateisystem eingebunden
werden.
In diesem Fall habe ich es einfach nach /mnt
gemountet.
# mount /dev/mapper/foobar /mnt
Auffuellen des Containers
Bei bereits verwendeten Medien haben wir das Problem, dass zwar neue Daten verschluesselt gespeichert werden, der Datentraeger u.U. aber noch unverschluesselte Daten von der vorherigen Nutzung enthaelt. Auch neue USB-Speichersticks sind bedingt gefaehrdet; diese sind ab Werk ueblicherweise mit Nullen bespielt. Das ist zwar nicht kritisch, liefert einem potenziellen Angreifer jedoch die Moeglichkeit herauszufinden, welche Bereiche auf dem Medium bereits verwendet werden und welche nicht, und damit evtl. Hinweise auf die Nutzung sowie eine Einschraenkung des Bereichs des Datenspeichers, der angegriffen werden muss.
Um dies zu verhindern gibt es drei allgemein uebliche Vorgehensweisen zur Wahl:
- Man ueberschreibt den Stick VOR dem Anlegen des Containers mit Zufallsdaten. Diese zu erzeugen ist jedoch sehr zeitaufwendig, zudem sind diese oft zyklisch wiederholt, wenn nicht ausreichend Entropie waehrend der Generierung zur Verfuegung steht, was einem Angreifer eine aehnliche Startsituation liefert wie ein mit Nullen beschriebenes Medium.
- Man beschreibt den bereits angelegten Containern mit Nullen - die sind schnell erzeugt und die Verschluesselung ist normalerweise um ein Vielfaches weniger aufwendig als das Erzeugen von Zufall. Hier laeuft man jedoch Gefahr, einem Angreifer ungewollt das Errechnen/Erraten des Schluessels zu vereinfachen, da dieser nun bei Kenntnis ueber das Vorgehen beim Anlegen des verschluesselten grosse Mengen gleicher Zeichen verschluesselt vorfindet und z.B. einfach davon ausgehen koennte, dass der Datentraeger am Ende mit geringster Wahrscheinlichkeit bereits beschrieben ist. So koennte er seinen Angriff darauf konzentrieren, so lange automatisiert potenzielle Schluessel durchlaufen zu lassen, bis nur noch gleiche Zeichen entschluesselt werden.
- Optimale Sicherheit erreicht man durch die Kombination der beiden genannten Methoden. Das ist jedoch extrem zeitaufwendig, vor allem bei langsamen Rechnern oder grossen Speichern.
Daher moechte ich hier zur Wahrung maximaler Sicherheit eine vierte Methode beschreiben: Wir erzeugen einen grossen Zufallsstring, dessen Groesse von Anwendung zu Anwendung verschieden und einem potenziellen Angreifer damit unbekannt ist. Dieser Zufallsstring wird dann wiederholt in den freien Platz des Containers geschrieben und damit zusaetzlich verschluesselt auf dem Datentraeger gespeichert.
Um dies durchzufuehren habe ich das kleine Perl-Script randfs geschrieben, das einen Zufallsstring zwischen 8MB und 16MB Groesse erzeugt und auf dem gemounteten Geraet wiederholt in einer Datei speichert, bis der Datentraeger voll ist. Anschliessend wird die Datei geloescht.
# ./randfs /mnt/riesenfile Randomize Free Space, (C)2006 Veit Wahlich <cru at zodia dot de> Generating 16236544 bytes urandom string... done Writing growing file to disk... 238 MB (33 MB/s) Writing cached data to disk... done Removing the growing file from disk... done
Feintuning
Da Ext3 ein Dateisystem ist das volle Permissions und Ownership (sowie noch tolle andere Dinge wie ACLs/Attrs) unterstuetzt, kann ein gewoehnlicher Benutzer nicht automatisch darauf schreiben.
Ich setze daher die Ownership des Mountpoints auf meinen Benutzernamen. Bei Ext2/Ext3 wird dies auch sauber beim naechsten Einbinden wieder so uebernommen.
# chown cru /mnt
Bei den meisten Linux-Distributionen beginnt der erste allgemeine Benutzer bei UID 500, bei manchen Distributionen (z.B. Debian) bei 1000. Daher ist es bei durch einzelne Benutzer genutzen Computern schon relativ wahrscheinlich, dass man mit UID 500 bereits am Ziel ist, auch wenn man mal an einen anderen Linux-Rechner geht.
Nun sorge ich noch dafuer, dass andere auf meinem System eingeloggte Benutzer nicht auf meinen USB-Stick zugreifen koennen:
# chmod 700 /mnt
Aushaengen und schliessen des Containers
Damit der USB-Speicherstick abgezogen werden kann, muss der LUKS-Container ausgehaengt
# umount /mnt
und anschliessend geschlossen werden.
# cryptsetup luksClose foobar
Integration in den Desktop
Verfuegt das System ueber HAL ab Version 0.5.7 und ist gegen cryptsetup-luks 1.0.1 oder hoeher gelinkt, kann es ueber den D-Bus Messagebus (ab Version 0.60, denke ich) den Benutzer am Desktop zur Eingabe des Passworts auffordern, sobald ein Wechselspeicher mit einer LUKS-Partition erkannt wird. Das Passwort kann dann fuer die gesamte Sitzung oder sogar persistent im Keyring des Desktops gespeichert werden.
Nach der Eingabe des (korrekten) Passworts wird das Dateisystem
im LUKS-Container genau wie die auf "normalen" Partitionen
automatisch unter /media
eingebunden.
Da ich meinen Dateisystemen Label gegeben habe, werden sie nach
/media/sehr sicher
und /media/unsicher
gemountet.
Im daraufhin automatisch oeffnenden Nautilus-Dateimanager sieht das dann so aus:
Diese Voraussetzungen sollten ansich bei allen aktuellen Desktop-Distri gegeben sein, in meinem Fall ist es ein Fedora Core 5 und es funktioniert out of the box. Ich habe es nur mit GNOME getestet, gehe aber davon aus, dass es zumindest mit KDE aehnlich unproblematisch funktionieren sollte.
Konfiguration von Udev
Aktuelle Udev-Installationen sind oft so konfiguriert, dass Device-Mapper-Geraete ignoriert werden. Das ist fuer bestimmte Konfigurationen wohl notwendig, verhindert aber leider auch das automatische Mounten des entschluesselten Containers. Bis Udev hier zwischen verschiedenen Device-Mapper-Geraeteklassen unterscheiden kann, hilft als Workaround das Deaktivieren der Ignoranz-Regel durch einfaches Auskommentieren mit "#". Bei Fedora Core 6 befindet sie sich in /etc/udev/rules.d/50-udev.rules in Zeile 165:
#KERNEL=="dm-[0-9]*", ACTION=="add", OPTIONS+="ignore_device"