Extrarunden: Aufrüstung gegen die Passwortknacker endlich auch bei SSH und anderen privaten Keys

Abstract

Die Performance des Passwortknackens ist in den letzten Jahren viele Tausend mal höher geworden, aber niemand kann sich wirklich bessere Passwörter merken als früher. Den Ausweg bieten Iterationsverfahren wie PDKBF2 oder bcrypt, aber gerade bei wirklich wichten Daten wie ssh-private-keys oder OpenVPN Schlüsseln hat man hier im Normalfall nicht die Optionen, die man haben sollte. Erste abhilfe bietet ein kleiner Patch...

Sicher gehashte Passwörter mit einem langen, zufälligen SALT und einem modernen Hashverfahren mit mindestens 128Bit sind heute Stand der Technik. Bis auf die Bitlänge von Salt und Hash hat sich da aber seit den 90ern nicht wirklich viel getan, während die Rechenleistung im Hashcracken um viele Grössenordnungen gewachsen ist.

Quasi unverändert ist hingegen die Speicherkapazität des menschlichen Gehirns, heisst: auch der Web 2.0 Mensch von 2013 kann sich nicht wirklich bessere Passwörter merken als sein Mosaic-surfendes alter Ego noch vor 20 Jahren - faktisch sind normale Hashes von merkbaren Passwörtern heute damit deutlich schwächer als damals. Denn das Salt hilft vor allem gegen das gleichzeitige Angreifen vieler Hashes, und die 128, 256 oder 512 Bit sind allesamt viel mehr, als jedes merkbare Passwort an Inhalt bietet.

Der Ausweg aus dem Dilemma: die Berechnung der Hashes künstlich deutlich schwerer machen. Faktisch passiert das durch mehrfache Anwendung, sogenannte Rounds oder Iterations. Statt einmal wird die Hashfunktion z.B. 2000 mal hintereinander angewendet. Effektiv verlangsamt das auch einen Angriff um grob diesen Faktor - Passwörter die vorher in einer Woche zu knacken waren brauchen nun fast 40 Jahre -- das lohnt sich.

Bei klassischen Unixpasswörtern ist dies auf modernen Systemen noch relativ einfach. In der /etc/login.defs kann über ein 256 Bit SHA Hash mit 10.000 Runden ausgewählt werden - dies betrifft alle ab da neu gesetzten Passwörter:

ENCRYPT_METHOD SHA256
SHA_CRYPT_MIN_ROUNDS 10000

Ohne die Rundenangabe wird bei SHA256 und SHA512 der Default von 5000 benutzt. Generell empfehlen sich solche Hashes überall dort, wo Loginpasswörter so gespeichert werden, also z.B. auch Mailaccounts oder Logins von Webanwendungen.

Prominente Systemkompromittierungen

Nun werden viele sagen: auf die Passworthashes in einer Datenbank oder der shadow-Datei sollte ohnehin niemand zugreifen können. Das stimmt natürlich - aber in der Vergangenheit gab es immer wieder prominente Fälle von Systemkompromittierungen, bei denen Hashes von abertausenden von Accounts entwendet wurden. Dies ist natürlich ohnehin der Super-GAU für jeden Serverbetreiber und schon peinlich genug - muss man nachher aber auch noch zugeben, die Hashes nicht zeitgemäss gesichert zu haben, ist das schon an der Grenze zur groben Fahrlässigkeit und kann ernste Konsequenzen haben, wie das Beispiel LinkedIn (siehe auch: LinkedIn wegen Passwort-Leck verklagt) letztes Jahr zeigte.

Ein ähnliches Problem stellt sich bei der Festplattenverschlüsselung - nur dass man da ja von vornherein davon ausgeht, dass ein potentieller Angreifer im Besitz der Daten ist. Entsprechend einfach haben es Linuxuser mit LUKS, mit dem Parameter --iter-time x bei einem cryptsetup luksAddKey-Kommando kann man eine Zeit (in Millisekunden) vorgeben, die iteriert werden soll.

Bereits wenige Sekunden, die man dann aber auch bei jedem Entsperren der Daten warten muss, bewirken da Millionen von Iterationen und bringen damit einen massiven Sicherheitsgewinn. Sind die Daten noch nicht abhanden gekommen, kann man auch problemlos einem bereits verschlüsselten Container so einen neuen Key verpassen und dann den alten mit weniger Iterationen entfernen.

Wer jetzt von Iterations so begeistert ist, dass er sie überall anwenden möchte, stößt dann aber schnell auf ein Problem, dass mein Anlass für diesen Artikel war: ssh-private-keys. Sie sind u.U. die wertvollsten private Keys die ein Systemadministrator verwaltet - erlauben sie doch den Zugriff auf teilweise dutzende und mehr Server, oft auf die eine oder andere Art bis hin zum direkten root-Zugriff. Nur: ssh-keygen kennt keine Option für Iterations!

Und wenn man genauer hinsieht stellt man fest: es gibt auch keinen Default! Es wird einfach nicht iteriert. Passphrases für ssh-keys müssen also wirklich gut sein, wenn sie einem gezielten Angriff lange widerstehen sollen.

Ein unhaltbarer Zustand!

Erste Recherchen ergaben schnell: die Situation ist nicht hoffnungslos. ssh-keygen kann es zwar nicht, aber OpenSSH kann normale von OpenSSL erstellte Keys im PKCS8 Format lesen - und hierbei kommen 2048 Iterationen nach dem etablierten PDKBF2 Verfahren zur Anwendung, also immerhin schonmal ein satter Gewinn. Einen Schlüssel nachträglich zu sichern ist einfach:

$ mv id_rsa id_rsa.old
$ openssl pkcs8 -topk8 -v2 des3 -in id_rsa.old -out id_rsa

Note

Und erst wenn man den neuen Key getestet hat zum sicher Löschen des alten shred -u id_rsa.old eingeben.

Man muss dabei die alte und natürlich eine neue (oder wieder die alte) Passphrase eingeben. OpenSSH kommt mit diesen neuen Schlüsseln problemlos klar - nur ssh-keygen kann sie eben nicht direkt erzeugen.

Das reichte mir nicht…

2048 - das ist angesichts heutiger Rechenleistungen wirklich wenig. Nur openssl bietet da keine Auswahlmöglichkeit! Also hilft nur eins: Open Source. Also erstmal den Source öffnen. Schnell ist die Stelle gefunden, an der man das Default verstellen kann, iter = PKCS12_DEFAULT_ITER; in apps/pkcs8.c - das ist auf 2048 definiert und man könnte hier einfach mehr eintragen und openssl neu bauen.

Etwas eleganter ist das Hinzufügen einer neuen Kommandozeilenoption, dafür habe ich mal einen Patch gebaut der auf die meisten grob aktuellen openssl Versionen passen sollte. -iters 100000 setzt die Iterationen dann z.B. auf 100.000.

--- openssl-1.0.0/apps/pkcs8.c  2010-01-22 21:17:29.000000000 +0100
+++ openssl-1.0.0/apps/pkcs8.c.iter     2013-03-27 00:42:09.808969739 +0100
@@ -157,6 +157,21 @@ int MAIN(int argc, char **argv)
                        topk8 = 1;
                else if (!strcmp (*args, "-noiter"))
                        iter = 1;
+               else if (!strcmp (*args, "-iters")) {
+                        if (args[1])
+                                {
+                                args++;
+                                iter = atol(*args);
+                                if (iter < 0)
+                                        {
+                                        BIO_printf(bio_err,
+                                                "Illegal iter count %s\n",
+                                                *args);
+                                        badarg = 1;
+                                        }
+                                }
+                        else badarg = 1;
+                        }
                else if (!strcmp (*args, "-nocrypt"))
                        nocrypt = 1;
                else if (!strcmp (*args, "-nooct"))

Das Gute ist: das gepatchte OpenSSL braucht man nur zur Schlüsselerstellung, nicht auf jedem System wo man die Schlüssel einsetzen will. Für openssh ist allerdings bereits Abhilfe in Form eines neuen Key-Formates in Arbeit wurde mir mitgeteilt. Und für openssh werde ich einen noch etwas umfangreicheren Patch an die Entwickler submitten, in der Hoffnung, dass mittelfristig so eine Option auch in den offiziellen Quellcode wandert.

Denn: Damit lassen sich nun nicht nur ssh private Keys wirklich sicher speichern. Auch der Private Key eines OpenVPN Netzwerkes - oder der SSL-Key des Mail, Web oder IMAP-Servers profitieren von einer sicheren Passphrase.

Note

Wobei der Nutzen bei letzteren je nach Szenario diskutierbar ist, das würde hier den Rahmen sprengen, aber in den Szenarien, wo man da eine Passphrase einsetzt, sind viele Iterationen oft besonders sinnvoll.

Bleibt nur noch eine Frage: wie viele Runden anwenden? In Umgebungen, in denen der Hash relativ häufig geprüft wird und ohnehin nur eine letzte Verteidigungslinie bietet, wie eben z.B. Logins, sollte man es nicht übertreiben. Letztendlich erhöht es die Serverlast und nach einem Einbruch muss man ohnehin mit kompromittierten Passwörtern rechnen. Das Default von 5000 klingt da durchaus vernünftig, auf Servern mit wenigen Logins pro Stunde kann man aber auch mehr nehmen.

Anders sieht es bei Daten aus, die vielleicht maximal ein paarmal am Tag entsperrt werden müssen, und bei denen das Passwort auch meist manuell eingegeben wird. Beispiele sind eben der ssh oder OpenVPN Key - oder auch SSL Keys. Hier dürfen es durchaus so viele Iterations sein, dass das Entsperren im Sekundenbereich dauert - das Eintippen des Passwortes dauert vermutlich immernoch länger. Und gerade drum ist das 2048 Default von OpenSSL wirklich untragbar - schön, dass man bei offener Software die Wahl hat.

Florian Kirstein, 03. May 2013