featured image Crypto 101

April 25, 2018 / von Oliver Milke / In Quality

Crypto 101

+++Der Originalartikel kann hier heruntergeladen werden:Zeitschriftenartikel, Veröffentlicht in Java Aktuell 01/2018.+++

Kryptographie ist ein sehr umfangreiches und vielschichtiges Thema, das viel Erfahrung und Wissen erfordert. Entwickler kommen im Alltag immer mal wieder mit Kryptographie in Berührungen, besonders im DevOps-Umfeld. Um die Artikellänge nicht zu sprengen und beim Wesentlichen zu bleiben, möchte ich mit diesem Artikel praxisrelevante Grundlagen für die tägliche Entwicklungsarbeit schaffen ohne dabei in die Tiefen von Security abzutauchen. Ich habe mir speziell jene Dinge vorgenommen, über die ich selbst während meiner Einarbeitungszeit in der Abteilung für Security der Mobilen Online Dienste bei VW gestolpert bin.

Kryptographie befasst sich mit Informationssicherheit und ist ein Teilbereich der Kryptologie. Ein weiterer Teilbereich ist Kryptoanalyse, deren Ziel es unter anderem ist, Schwächen in bestehenden kryptographischen Verfahren aufzudecken oder den gebotenen Schutz zu quantifizieren. Sicherheit (Security, secure) wiederum umfasst neben Kryptographie noch weitere Aspekte wie zum Beispiel Prozesse und Umgang mit den Methoden der Kryptographie sowie die Bewusstheit solcher Prozesse. Der Form halber ergänze ich hier noch, dass Sicherheit (Safety, safe) eher Unversehrtheit beschreibt und demnach zu einem ganz anderen Bereich gehört. Die Unterscheidung ist im Deutschen leider nicht so deutlich.

Kryptographie wird eingesetzt, um 3 Ziele zu erreichen:

  • Vertraulichkeit
    • Die Gewissheit, dass Nachrichten nicht mitgelesen werden.
  • Integrität
    • Die Gewissheit, dass Nachrichten nicht verändert sind.
  • Authentizität
    • Die Gewissheit, dass Nachrichten von einem mir bekannten Absender stammen.

Kryptographische Primitiven

Die im vorhergehenden Abschnitt dargestellten Ziele werden durch verschiedene Operationen, die sogenannten kryptographischen Primitiven, ermöglicht. Man kann sie sich als die Grundrechenarten der Kryptographie vorstellen.

  1. Kryptographischer Hash Einwegfunktionen, die neben weiteren Eigenschaften vor allem kollisionsresistent sind.
  2. Symmetrische Verschlüsselung Verschlüsselungsverfahren, bei denen derselbe Schlüssel sowohl für die Verschlüsselung als auch für die Entschlüsselung verwendet wird. Diese Verfahren sind typischerweise sehr schnell.
  3. Asymmetrische Verschlüsselung Verschlüsselungsverfahren, bei denen es zwei voneinander abhängige Schlüssel gibt, wobei der jeweils andere Schlüssel die Umkehr einer Operation erlaubt. Auch Public-Key-Verfahren genannt. Diese Verfahren sind typischerweise langsamer als symmetrische Verfahren.
  4. Digitale Signatur Zur Sicherstellung von Authentizität und Integrität einer Nachricht. Vereinfacht dargestellt handelt es sich um die asymmetrische Verschlüsselung des Hashs einer Nachricht.
  5. Kryptographisch sichere Zufallszahlengenerierung Man kann sich leicht vorstellen, dass echter Zufall durch eine deterministische Maschine nicht leistbar ist. Dennoch benutzen kryptographische Verfahren Noncen (Zahlen, die nur ein einziges Mal eingesetzt werden dürfen) oder Initialisierungsvektoren (Startblock bei Block-Chiffren). Sie werden typischerweise zufällig generiert und haben somit einen Einfluss auf die Sicherheit anderer Operationen.
  6. Weitere Es gibt noch weitere Primitiven, auf die ich hier nicht eingehen will. Einen Einstiegspunkt für Interessierte bietet der gleichnamige Wikipedia-Artikel.

Die Kombination solcher Primitiven nennt man Krypto-System. Die Art und Weise der Kombination hängt vom Anwendungsfall ab, zum Beispiel Transport-Layer-Security (TLS) oder Secure Shell (SSH).

Sicherheit von Kryptographie

Die Sicherheit von kryptographischen Verfahren basiert zu großen Teilen auf Einwegfunktionen. Wie der Name bereits suggeriert handelt es sich um Funktionen, zu denen (noch) keine effiziente Umkehrfunktion vorhanden ist. Beispiele für Einwegfunktionen sind neben den kryptographischen Hashfunktionen auch die Multiplikation von Primzahlen (Grundlage für RSA) sowie die diskrete Exponentialfunktion in endlichen Körpern oder die Multiplikation von Punkten auf elliptischen Kurven (Grundlage des ElGamal-Verfahrens und des Diffie-Hellman-Verfahrens).

Das Prinzip von Kerckhoff besagt, dass die Sicherheit von kryptographischen Verfahren nicht von der Geheimhaltung des Verfahrens abhängen darf. Vielmehr muss es von der Geheimhaltung des Schlüssels abhängen. Dadurch entsteht die Möglichkeit zur Widerlegung der systematischen Sicherheit. Unsichere Verfahren können dann infolgedessen vermieden werden. Das Gegenteil bezeichnet man landläufig als „Security By Obscurity“.

Schwachstellen in Krypto-Systemen entstehen entweder direkt in der Spezifikation (BEAST) eines Verfahrens oder in einer konkreten Implementierung eines Verfahrens (Heart Bleed). Implementierungen können außerdem Seitenkanal-Angriffe ermöglichen, indem zum Beispiel aus der benötigten Verarbeitungszeit einer Operation Schlussfolgerungen hergeleitet werden können.

Passwörter sicher speichern

Die von einem Benutzer gewählten Passwörter in Klartext zu speichern ist ein „No Go“ – das wissen die meisten Entwickler. Aber wie genau speichert man denn Passwörter? Oft habe ich schon den Begriff Verschlüsselung in diesem Zusammenhang gehört, wobei das typischerweise eines von zwei Dingen bedeutet. Entweder verkompliziert man die Passwortspeicherung, weil man jetzt auch noch „das Passwort“ für eine Verschlüsselung speichern muss oder Verschlüsselung wird fälschlicherweise verwendet und meint eigentlich Hashing.

Aufgrund der Tatsache, dass Hashes deterministische Einwegfunktionen sind, sind sie hervorragend geeignet, um Passwörter abzusichern. Allerdings kann man daher einfach Hash-Tabellen bilden, um einmal berechnete Hashes in Zusammenhang mit ihrem Klartext-Ursprung zu speichern. Auf diese Weise entsteht eine zeit-effiziente Umkehrfunktion. Für den Fall, dass man zum Beispiel eine ganze Datenbank mit gehashten Passwörtern vorliegen hat (bspw. durch den Einbruch in ein System) entsteht ein zusätzliches Problem. Rät man Passwörter und hasht sie (teuer), kann man alle vorhandenen Passwörter dagegen prüfen (günstig) und somit in einem Schritt alle Passwörter angreifen.

Abhilfe schafft ein sogenannter Salt. Salts sind zufällig und pro Passwort individuell zu generieren, zusätzlich mit dem Passwort zu speichern und reichern das Passwort vor dem Hashen an. Dadurch unterscheiden sich Passwörter mindestens um ihren Salt, was zwei wesentliche Effekte hat. Zum einen sieht man zwei Datensätzen nicht mehr sofort an, ob es sich um das gleiche Passwort handelt. Zum anderen ist es dadurch nicht mehr möglich alle Passwörter einer Datenbank gleichzeitig zu überprüfen.

Hashfunktionen werden jedoch auch zu anderen Zwecken auf deutlich größere Datenmengen angewendet, weswegen eines der verfolgten Entwicklungsziele für manche Hashfunktionen Performance ist. Das ist unglaublich praktisch, um den Hash von einem Linux-Image zu berechnen und Passwörter mittels Brute Force stumpf auszuprobieren. Deswegen ist eine wichtige Anforderung an Hashfunktionen für Passwörter Langsamkeit. Eine einfache Lösung ist die wiederholte Anwendung einer Hashfunktion. Die bessere Alternative ist eine Hashfunktion, bei deren Entwicklung darauf geachtet wurde, dass sie konfigurierbar langsam ist. Wenn eine Hashfunktion gleichzeitig noch sehr viel Arbeitsspeicher benötigt, wird die Parallelisierung oder der Einsatz von spezieller Hardware unwirtschaftlich.

Eine für Passwörter geeignete Hashfunktion ist bCrypt. Sie erzeugt aus einem Salt, einen Kostenfaktor und dem Passwort einen String, der sowohl den Salt als auch den Hash enthält. 12 ist ein guter Kostenfaktor für den Moment.

Im Security-Stackexchange findet ihr eine sehr ausführliche Antwort, die weitere gute Hashfunktionen vorstellt und eine ausführliche Darstellung bietet.

Host-Authentifizierung bei SSH

Die meisten Entwickler haben sich schon mal per SSH auf einem Server eingeloggt. Bei der ersten Verbindung zu einem SSH-Server wird man typischerweise gefragt, ob man dem Server vertrauen möchte - na klar möchte man das, oder vielleicht doch lieber nicht?

Bei der Verbindungsinitiierung stellt sich der Server vor, indem er seinen öffentlichen Schlüssel bereitstellt. Anhand des öffentlichen Schlüssels kann der Client entscheiden, ob er den Verbindungsaufbau fortführen will, weil er den Schlüssel kennt - im Grunde wie die Gästeliste für eine VIP-Party. Im weiteren Verlauf des Handshakes beweist der Server, dass er im Besitz des zugehörigen privaten Schlüssels ist.

Idealerweise erfolgt der Austausch des öffentlichen Schlüssels über einen anderen Kanal als bei der ersten Verbindung, denn dieser öffentliche Schlüssel ist der Vertrauensanker für die Authentifizierung des Servers gegenüber dem Client. Eine Möglichkeit ist ein USB Stick. Ist das nicht möglich, sollte man sich vom Administrator beispielsweise per Telefon den Fingerprint (Hash) des öffentlichen Schlüssels bestätigen lassen, um sicherzugehen, sich nicht mit einem Fake-Server zu verbinden. Das ist beim heimischen Raspberry Pi sicherlich wenig kritisch, anders als bei der Verbindung zu einem Remote Backup Server, zu dem das eigene Backup übertragen wird.

Host-Authentifizierung bei TLS

Die Anzahl der SSH-Server, zu denen man sich regelmäßig verbindet, ist überschaubar. Etwas anders gelagert ist die Situation bei Webseiten. Jeden einzelnen öffentlichen SChlüssel einer Webseite vorab zu speichern ist einfach unpraktikabel. Dennoch besteht der Bedarf zu entscheiden, ob eine Webseite diejenige ist, für die sie sich ausgibt, insbesondere dann, wenn Kreditkarten-Daten oder andere sensible Informationen ausgetauscht werden. An dieser Stelle wird das Konzept von Schlüsselpaaren einfach mehrstufig erweitert und das Ergebnis ist eine Zertifikatskette.

An die Stelle eines öffentlichen Schlüssels für jeden Server tritt ein öffentlicher Schlüssel einer Zertifizierungsstelle (Certificate Authority, CA). Dieser öffentliche Schlüssel ist mit dem zugehörigen privaten Schlüssel signiert und wird Root-Zertifikat genannt. Mit genau diesem privaten Schlüssel sind weitere öffentliche Schlüssel signiert, die Intermediate Certficates. Es gibt typischerweise mehrere Stufen von Intermediate Certificates. Im Wesentlichen wird dadurch das Ziel verfolgt, den zentralen privaten Schlüssel so selten wie möglich aus dem Tresor zu holen.

Ein Schlüsselpaar ist entweder zur Ausstellung weiterer Intermediate Certificates oder zur Verwendung für eine Webseite gedacht.

Durch diese Verkettung muss der Client nur dem Root-Zertifikat vertrauen und kann dennoch sichergehen, dass der Webserver der richtige ist. Zusammengefasst bedeutet das, dass ein Zertifikat ein öffentlicher Schlüssel eines asymmetrischen Verfahrens ist, der seinerseits mit einem asymmetrischen Verfahren signiert wurde. Mittels eines Certificate Signing Request (CSR) wird um diese Signierung eines öffentlichen Schlüssels gebeten und gleichzeitig der Beweis geführt, dass der Antragsteller im Besitz des zugehörigen privaten Schlüssels ist. Der CSR enthält daneben noch weitere Informationen, beispielsweise die URL der Webseite, für die das Zertifikat genutzt werden soll, und wer es beantragt.

Cipher Suites am Beispiel am von TLS

Anleitungen zur Konfigurationen von Web-Servern enthalten regelmäßig auf den ersten Blick unleserliche Zeichenfolgen, wie zum Beispiel ECDHE_RSA_WITH_AES_256_CBC_SHA384.
Es ist allgemein bekannt, dass TLS-Verbindungen verschlüsselt sind. Das konkrete Verschlüsselungsverfahren wird jedoch durch die Cipher Suite festgelegt. Im gewählten Beispiel oben erfolgt die Verschlüsselung mit AES (CBC-Modus, 256 Bit Schlüssel). Zusätzlich wird ein HMAC auf Basis von SHA-384 gebildet, der die Integrität und Authentizität der vertraulichen Inhalte sichergestellt.

Woher kommt jedoch der verwendete Schlüssel, der dem Server und dem Client vorliegen muss? Dieser wird mithilfe des Diffie-Hellman-Verfahrens auf der Basis der diskreten Exponentialfunktion auf elliptischen Kurven bestimmt. Dieses mathematische Verfahren ermöglicht es, dass sich zwei Parteien über einen unsicheren Übertragungsweg dennoch auf einen wirklich geheimen Schlüssel einigen können. Weiterhin überprüft der Browser mit dem RSA-Verfahren, ob der Server wirklich derjenige ist, für den er sich mit seinem Zertifikat ausgibt. An dieser Stelle muss der Server unter Beweis stellen, dass er im Besitz des passenden privaten Schlüssels ist.

Schlüsselgenerierung und Zertifikatsprüfung finden als Teil des TLS-Handshakes statt und sind die Grundlage für die anschließend symmetrisch verschlüsselte Kommunikation.

Ganz allgemein ausgedrückt legt eine Cipher Suite fest, welche kryptographischen Primitiven in einem Protokoll zum Einsatz kommen. Welche Elemente nötig sind hängt dann wiederum vom Protokoll ab, deswegen unterscheiden sich zum Beispiel die Cipher Suites zwischen TLS 1.2 und TLS 1.3.

Weiterführendes Material und Tools

Unabhängig von der Frage, ob Passwörter durch die Anwendung hinreichend sicher gespeichert werden, besteht meine Empfehlung keine schwachen Passwörter zu erlauben und starke Passwörter zu ermöglichen (bedeutet u. a. nicht auf 12 Zeichen zu begrenzen). Für eine einfache Realisierung von Passwort Policies in Java eignet sich passay.

Wenn Kryptographie Teil des Anwendungsfalls ist, ist man mit Bouncy Castle gut bedient. Darin ist auch eine Implementierung von bCrypt enthalten.

Einen niederschwelligen Einstieg in die sichere Konfiguration von gängigen Webservern bietet der Mozilla Config Generator. Die Konfiguration kann anschließend mit dem Qualys SSL Lab Server Test bewertet werden. Diese Überprüfung lässt sich übrigens auch dank des offiziellen API prima mit dem gewünschten Monitoring Tool automatisieren.

Bei der Entscheidung welches Verfahren eingesetzt werden kann und wie viele Bit ein Schlüssel haben sollte, hilft keylength.com. Hier sind unter anderem die Empfehlungen von NIST und BSI zusammengetragen.

Im Blog von Bruce Schneier findet ihr viel zum Thema Security aus der Perspektive eines sehr erfahrenen Kryptographen.

Fazit

Die grundlegenden Konzepte der hier vorgestellten kryptographischen Aspekte lassen sich gut überblicken. Ich habe bewusst viele Details ausgelassen, um einen Überblick zu ermöglichen. Eine detaillierte Betrachtung wird schnell sehr umfangreich. Die Folien zum Vortrag für diesen Artikel sind auf meinem Speaker Deck verfügbar.
Für den Umgang mit Kryptographie möchte ich euch noch zwei Direktiven ans Herz legen.

  1. selbstgemacht ist schlecht
  2. neu ist schlecht

Dabei geht es vor allem darum, dass die eingesetzten Verfahren gründlich geprüft sind, was typischerweise weder bei neuen noch bei selbstgemachten Verfahren der Fall ist.
Die zur Verfügung gestellten Links helfen, mit Aufwand sichere Webserver bereitzustellen.


Oliver Milke
Oliver Milke

- Software Development -

Olli codet aus Leidenschaft seit über 10 Jahren. Sein Fokus liegt auf Java-Backend-Entwicklung und darauf, auch gerne mal über den Tellerrand zu schauen. Nebenbei ist er Co-Organisator der JUG-Ostfalen.