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:

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:

  1. 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.
  2. 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.
  3. 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.

LUKS-Passwortabfrage

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:

LUKS im Nautilus

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"