Java für Fortgeschrittene Proseminar im Sommersemester

Transcription

Java für Fortgeschrittene Proseminar im Sommersemester
Java für Fortgeschrittene
Proseminar im Sommersemester 2009
Java und Kryptographie
Markus Dauberschmidt, [email protected]
Technische Universität München
15.06.2009
Zusammenfassung
Der Kryptographie kommt in der Software-Entwicklung eine steigende Bedeutung zu, nicht nur
für Transaktionen z.B. in Online-Shops, sondern auch zum Absichern privater Daten wie Passwörter.
Der vorliegende Artikel beschäftigt sich damit, auf welche Weise Kryptographie in Java-Applikationen
integriert werden kann. Dazu werden zunächst die Grundlagen der symmetrischen und asymmetrischen Verschlüsselung erläutert. Des Weiteren wird die Verwendung von digitalen Signaturen und
Zertikaten dargestellt. Darüber hinaus zeigt der Artikel sowie ein zugehöriges Demo-Projekt, wie
diese Themen in Java mit Hilfe der Bouncy Castle API bzw. dem JCE genutzt werden können.
1 Wozu eigentlich Kryptographie?
Der Wunsch des Menschen, Geheimnisse vor den Augen neugieriger Mitmenschen zu verstecken, geht
bis in die Antike zurück. Bereits zu Cäsars Zeiten nutzte man Verschlüsselung (Caesar Chire ), um
Botschaften über die römischen Truppenbewegungen zu sichern. Auch in späteren Jahrhunderten war
die Kryptographie häug im militärischen oder politischen Umfeld anzutreen. Gerade im letzten Jahrhundert hat der Bereich der Verschlüsselung eine geradezu rasante Entwicklung hinter sich. Militärische
Verschlüsselungsmaschinen wie die bekannte Enigma oder die Lorenz-Maschine waren groÿen Teilen
der Öentlichkeit ein Begri. Der kometenhafte Aufstieg der IT in späteren Jahren sorgte für einen rasanten Entwicklungsschub, da sich nun komplexe Algorithmen in kürzester Zeit in Computern umsetzen
lieÿen. Brilliante Köpfe entwickelten ganz neue Methodiken, Daten zu verschlüsseln: die asymmetrische
Kryptographie, welche heute eine nach wie vor herausragende Rolle bei der sicheren Kommunikation spielt
(S/MIME, HTTPS, etc.). Sie schufen Lösungen für das alte Problem des sicheren Schlüsselaustauschs
(Die Helmann Schlüsselaustausch [DH76]).
Auch im nichtmilitärischen Bereich sorgte die zunehmende Globalisierung der Wirtschaft für einen
gesteigerten Bedarf an Lösungen für den sicheren Austausch von Informationen, um sich vor Wirtschaftsspionage und fremden Nachrichtendiensten zu schützen. Dass diese Sorgen nicht unbegründet sind, zeigt
der Fall der Firma Siemens aus dem Jahre 1993: Der Abschluÿ eines Entwicklungsauftrages für einen
Hochgeschwindigkeitszug für Südkorea scheiterte deshalb, weil es dem französischen Geheimdienst gelungen war, ein gefaxtes Angebot der Firma Siemens abzufangen. Das ermöglichte es der französischen
Konkurrenz, ein besseres Angebot abzugeben. Ein Fall, der ein Milliardengeschäft verhinderte [Ulf99].
Doch nicht nur in der Industrie erfreut sich die Kryptographie steigender Beliebtheit, auch der private
Nutzer setzt immer häuger Kryptographie ein, ob zur Verwaltung seiner Passwörter oder zum Austausch
von privaten Nachrichten mit Freunden und Bekannten. Natürlich hat auch der .com Hype und der seitdem wachsende Trend zu Online-Shopping sein übriges getan. Niemand würde heute freiwillig mehr seine
Bankdaten oder seine Kreditkartennummer unverschlüsselt an einen Online-Shop senden. Die Nutzung
von (starker) Kryptographie durch Dienstleister und eShops ist mittlerweile zu einer Grundvoraussetzung
für ein tragendes Geschäftsmodell geworden.
Unter den Begri Kryptographie fällt nicht nur die reine Datenverschlüsselung, sondern u.a. auch die
Integritätsüberprüfung der Daten. Mit Hilfe von sog. digitalen Signaturen ist es heute möglich, die Unverfälschtheit einer Information zu verizieren. So können sich zum Beispiel Bank und Bankkunde sicher
sein, dass ein Überweisungsauftrag nicht auf dem Weg durch das Internet zum Bankserver unerkannt
verändert wurde und nun ein falscher Betrag auf einem fremden Konto landet.
1
Im Folgenden werden zunächst die beiden groÿen Klassen der symmetrischen und asymmetrischen Verschlüsselung behandelt und deren Unterschiede erläutert. Nach einem Abriss über digitale Signaturen und
Hashs wird die Bouncy Castle API vorgestellt, die dem Java-Programmierer ein mächtiges Werkzeug an
die Hand gibt, Kryptographie in seine Anwendungen zu integrieren. Es werden hierbei die grundlegenden
Funktionen zur symmetrischen bzw. asymmetrischen Verschlüsselung, zur Generierung von Schlüsselmaterial und zur Erstellung von Signaturen betrachtet. Darauf folgt eine Erläuterung, worum es sich bei
Zertikaten handelt und was CAs sind. Der Artikel schlieÿt mit einem Überblick über Kryptographie in
Hardware.
2 Symmetrische und asymmetrische Verschlüsselung
Gemeinhin kann man zwei groÿe Klassen der Verschlüsselung unterscheiden: Die symmetrische und die
asymmetrische Verschlüsselung.
2.1 Symmetrische Verschlüsselung
Bei der symmetrischen Verschlüsselung kennen sowohl Sender als auch Empfänger das Geheimins (Schlüssel), welches zum Verschlüsseln der Daten verwendet wurde. Man spricht hier auch von einem shared
secret. Zum Entschlüsseln kommt der gleiche Schlüssel zum Einsatz wie auch beim Verschlüsseln.
Abbildung 1: Symmetrische Verschlüsselung
Die symmetrische Verschlüsselung selbst kann man wiederum in zwei groÿe Klassen einteilen: StreamChiren und Block-Chiren. Diese beiden Verfahren arbeiten grundsätzlich verschieden:
2.1.1 Block-Chiren
Wie der Name suggeriert, werden bei einer Block-Chire jeweils Blöcke fester Länge der Eingabedaten
verschlüsselt. Ist der letzte Block kleiner als die Blockgröÿe der Chire, so muss ein sog. Padding erfolgen, d.h. der Block muss bis zur benötigten Gröÿe mit Bytes in einer bestimmten Form aufgefüllt werden.
Arbeits-Modus
Normalerweise wird nie jeder Block für sich einzeln genommen verschlüsselt, sondern es kommt ein
sogenannter Arbeitsmodus zum Einsatz, welcher die einzelnen Klartext- bzw. Chireblöcke zueinander in
Beziehung setzt.
Der einfachste Modus ist der ECB, der Electronic Code Book Modus. Hier wird wirklich jeder
Block für sich separat verschlüsselt. Das groÿe Problem hierbei ist, dass gleiche Eingabeblöcke auch
immer gleiche Ausgabeblöcke produzieren und damit den Chiretext anfällig für z.B. statistische Analysen
machen!
Um dies zu verhindern bzw. zu erschweren, gibt es verschiedene andere Arbeitsmodi, von denen der
CBC vorgestellt werden soll. CBC steht für Cipher Block Chaining und bedeutet, dass jeder KlartextBlock zunächst noch mit dem vorherigen Chireblock XOR verknüpft wird, bevor er verschlüsselt wird.
Auf diese Weise ist das Ergebnis also immer vom vorherigen Block abhängig und Chiretext-Analysen
werden stark erschwert. Ein Problem existiert natürlich am Anfang: Womit wird der allererste Block
verknüpft? Hier kommt der sogenannte IV, der Initialisierungsvektor, ins Spiel. Dieser liefert die Daten,
2
Abbildung 2: Arbeitsweise des ECB Modus
mit denen der erste Klartextblock XOR verknüpft wird, bevor er danach verschlüsselt wird. Der IV muss
nicht geheim gehalten werden, sollte aber möglichst zufällig gewählt sein.
Abbildung 3: Arbeitsweise des CBC Modus
Sind die Blöcke bei der Ver- und Entschlüsselung voneinander abhängig, können sich Bitfehler bei der
Übertragung natürlich katastrophal auswirken. Beim CBC ist es jedoch so, dass ein Bitfehler im Block N
immer nur den nachfolgenden Block N + 1 verfälscht, sich Fehler also nicht beliebig lange fortpanzen.
Neben diesen beiden Modi gibt es weitere, die aus der Blockverschlüsselung de facto eine Stromverschlüsselung machen. Hierzu zählen zum Beispiel die Modi OFB (Open Feedback), CFB (Cipher
Feedback) und CTR (Counter Mode). Mehr zu diesen Modi ndet man in der einschlägigen Literatur
unter [Sch96], [Sch07].
Die wohl bekanntesten Vertreter der Klasse der Blockchiren sind AES (Rijndael), DES, 3DES, Twosh und Blowsh.
Die Rijndael-Chire wurde im Rahmen einer NIST Ausschreibung zum Sieger eines Wettbewerbes
gewählt, bei dem es darum ging, einen Nachfolger für den bis dahin dominierenden Verschlüsselungsalgorithmus DES bzw. 3DES zu nden. Rijndael hat sich gegen eine Reihe von Chiren von bekannten
Persönlichkeiten der Kryptowelt (Ron Rivest, Bruce Schneier) durchsetzen können und wurde dadurch
der neue Standard (AES = Advanced Encryption Standard) was symmetrische Verschlüsselung angeht.
AES wird heute in so gut wie jeder Anwendung unterstützt, die symmetrische Verschlüsselung einsetzt.
Eine Erläuterung, wie der AES funktioniert, ndet sich unter [Zab].
2.1.2 Strom-Chiren
Im Gegensatz zu einer Block-Chire muss bei einer Strom-Chire nicht darauf gewartet werden, dass
sich genügend Klartext-Daten angesammelt haben, so dass ein Block verschlüsselt werden kann, sondern
die Verschlüsselung kann sofort beim ersten Bit begonnen werden. Hierdurch ergibt sich ein kleiner
Geschwindigkeitsvorteil, der Strom-Chiren besonders für Echtzeitübertragungen sinnvoll macht, wie
3
beispielsweise im Mobilfunk zur Verschlüsslung von Gesprächsdaten. Die Klartextdaten werden beim
Einsatz einer Strom-Chire mit einem Bitstrom (auch Schlüsselstrom genannt) XOR verknüpft. Die
Entwicklung des Schlüsselstroms hängt dabei von den bisher verschlüsselten Daten ab.
Strom-Chiren können sehr sicher verwendet werden, jedoch nur, wenn beachtet wird, dass derselbe
Startzustand (Initialization vector, IV) nie mehrfach verwendet wird. Die Sicherheit einer Strom-Chire
kann der idealen Sicherheit eines One-Time-Pad nahe kommen.
Einem One-Time-Pad (OTP) liegt kein fester Algorithmus zugrunde, sondern der Schlüsselstrom wird
komplett zufällig gebildet. Da durch die Zufälligkeit kein Ansatz zur Ermittlung des Schlüssels gegeben
ist, gilt ein OTP als unbrechbar sofern echte Zufallszahlen zum Einsatz kommen. In der Praxis werden OTPs jedoch sehr selten verwendet, da die wirklich zufällige Generierung von ausreichend langen
Zufallszahlenketten alles andere als trivial ist. Generatoren, die Zahlen zufällig rein durch SoftwareAlgorithmen bestimmen, nennt man pseudo-random, da sie nie wirklich zufällig sind. Echte Zufallszahlen erzeugt man durch die Einbeziehung nichtdeterministischer externer Einüsse, wie z.B. Sensoren,
die Spannungsschwankungen oder Temperaturunterschiede an Motherboard-Komponenten miteinbeziehen. Ein interessanter Artikel ndet sich unter [tru] oder auch in [Lau09].
Allen Verfahren der symmetrischen Verschlüsselung ist gemein, dass sie in der Regel eine sehr hohe
Verschlüsselungs- und Entschlüsselungsgeschwindigkeit erreichen, d.h. auch das Verschlüsseln von groÿen
Datenmengen ist hiermit performant möglich. Dies liegt darin begründet, dass normalerweise für die Verschlüsselungsvorgänge relativ einfache arithmetische Operationen wie Multiplikationen und Additionen
verwendet werden, sowie Ersetzungen der Daten vorgenommen werden (Substitutionsschritte und Diusion). Ein weiterer Vorteil ist darin zu sehen, dass aufgrund der Einfachheit der algebraischen Umformungen
der Verschlüsselungsalgorithmus in der Regel auch sehr gut in Hardware umgesetzt werden kann und hiermit sehr hohe Geschwindigkeiten möglich sind. Die Schlüssellängen bei Block-Chiren sind, als bei der
asymmetrischen Verschlüsselung, relativ kurz mit in der Regel 128, 192 oder 256 Bit Schlüssellänge.
In der Natur des shared secrets, welches dem Empfänger und dem Sender der Nachricht vorliegen
muss, liegt aber auch das gröÿte Problem der symmetrischen Kryptographie. Das gemeinsame Geheimnis muss zunächst auf einem sicheren Weg zwischen den beiden Kommunikationspartnern ausgetauscht
werden! Erlangt ein Angreifer bei diesem Austauschprozess Kenntnis von dem geheimen Schlüssel, ist die
Verschlüsselung zwecklos, da der Angreifer die Daten nun jederzeit entschlüsseln kann (je nach Vorgehen).
Eine Abhilfe für dieses Dilemma fand im Jahre 1976 das Forscherduo Whiteld Die und Martin
Hellman mit der Erndung des Die-Hellman Algorithmus zum sicheren Schlüsselaustausch (für weitere
Information siehe [DH76]). Diese Forschungsarbeiten bildeten das Fundament für die asymmetrische
Verschlüsselung.
2.2 Asymmetrische Verschlüsselung
Der grundlegende Unterschied der asymmetrischen Verschlüsselung zur symmetrischen Verschlüsselung
besteht darin, dass für die Ver- und Entschlüsselung zwei unterschiedliche Schlüssel zum Einsatz kommen.
Man spricht von einem Schlüsselpaar und von dem privaten Schlüssel und dem öentlichen Schlüssel. In
Anlehnung an diese Namensgebung bezeichnet man die asymmetrische Verschlüsselung auch häug als
private/public key cryptography. Für die konsequente Nutzung wird eine sogenannte PKI (Private/Public
Key Infrastructure) benötigt.
Den Teil des Schlüssels, der zum Entschlüsseln der Daten verwendet wird, bezeichnet man als Private
Key. Dieser wird, wie der Name schon suggeriert, geheim gehalten. Der Schlüsselteil, mit dem die Daten
an den Besitzer des Private Keys verschlüsselt werden, bezeichnet man als Public Key. Dieser ist, wie
ebenfalls am Namen ersichtlich, öentlich. Häug werden die öentlichen Schlüssel in sogenannten Key
Respositories im Internet hinterlegt (häug sind dies LDAP-Verzeichnisse).
Ein Verfahren, welches Daten asymmetrisch verschlüsselt, ist das RSA-Kryptosystem [RSA78], dessen
Bezeichnung sich aus den Anfangsbuchstaben der Namen seiner Ernder Ron Rivest, Adi Shamir und
Leonard Adleman ableitet. Eine kurze und leicht verständliche Einführung ndet sich unter [rsa], [Sch07],
[Sch96] und [Sin01].
Im Gegensatz zur symmetrischen Verschlüsselung setzt die asymmetrische Verschlüsselung auf komplexe Operationen zur Verschlüsselung. Eine asymmetrische Verschlüsselung von Daten ist daher häug
wesentlich teurer in der Ausführung als eine symmetrische Verschlüsselung. Ebenso werden bei der asymmetrischen Verschlüsselung weitaus längere Schlüssellängen verwendet (i.d. R. normalerweise 1024 Bit,
häug auch 2048 Bit, selten 4096 Bit). Dies liegt darin begründet, dass die Sicherheit des Verfahrens
4
zu einem guten Stück auch von der Schlüssellänge abhängt. In der Praxis wird die asymmetrische Verschlüsselung daher häug nur auf relativ kleine Datenmengen angewandt und mit der symmetrischen
Verschlüsselung kombiniert: Die eigentliche Datenverschlüsselung der Nutzdaten geschieht mit einem
symmetrischen Verfahren (wie z.B. dem AES), welches eine hohe Geschwindigkeit bietet. Der hierfür
verwendete Schlüssel wird aber dann wiederum stark asymmetrisch (z.B. mit RSA) verschlüsselt. Häug
gibt es auch noch den Fall, dass der symmetrische Schlüssel nach regelmäÿigen Intervallen (z.B. 1 Stunde,
1 MByte Nutzdaten, o.ä.) gewechselt wird, um die Sicherheit weiter zu erhöhen. In den letzten Jahren
hat vor allem auch die ECC, die Elliptic Curve Cryptography, von sich reden gemacht, die aufgrund
eines anderen Ansatzes eine ähnliche Sicherheit bei weitaus höherer Verschlüsselungsgeschwindigkeit und
verringerter Schlüssellänge bietet ([Sch07]).
3 Digitale Signaturen & Hashs
Mit Hilfe einer Digitalen Signatur ist es möglich, die eingangs erwähnte Verikation von übermittelten
Daten durchzuführen. Die Signatur ist gewissermaÿen das Spiegelbild zur asymmetrischen Verschlüsselung, da hier im Endeekt nur der Einsatz der Schlüssel vertauscht wird. Für die Erstellung der Signatur
wird der private Schlüssel verwendet und für die Verikation kommt der öentliche Schlüssel zum Einsatz.
Da es aufgrund der Nachteile der asymmetrischen Kryptographie jedoch extrem zeitaufwändig wäre,
die gesamten Daten mit dem Private Key zu verschlüsseln (=signieren), bedient man sich einer Abkürzung:
Es wird ein Hash/Digest, ein digitaler Fingerabdruck, der Daten genommen und dieser wird verschlüsselt. Dieser Digest wird mit Hilfe einer kryptographischen Hashfunktion gebildet. Das Ergebnis der
Anwendung einer kryptographischen Hashfunktion auf einen Eingabetext ist eine Hexzahlenfolge, welche
mit üblicherweise 16, 20 oder 32 Byte sehr kurz ist und den gesamten Text repräsentiert. Das Besondere
ist, dass das Verändern auch nur eines einzigen Bits im Eingabetext (Ändern einer Zahl, Entfernen eines
unsichtbaren Whitespaces o.ä.) zu einem völlig veränderten Digest führen wird.
Dieser Hash wird nun mit Hilfe des Private Keys signiert. Zur Verikation der Signatur geht der
Empfänger wie folgt vor: Er bildet seinerseits den Hash über die Nachricht, entschlüsselt bzw. veriziert
die Signatur mit Hilfe des Public Keys des Senders und vergleicht den erhaltenen Wert mit seinem
eigenen errechneten Hashwert. Stimmen beide Werte überein, so kann er sich (relativ) sicher sein, dass
die Nachricht nicht verändert wurde, während eine Abweichung ein Indiz dafür ist, dass irgendetwas an
den Daten manipuliert wurde.
Es gibt heutzutage einige kryptographische Hashfunktionen, unter denen man wählen kann. Der am
häugsten eingesetzte Hashalgorithmus ist seit vielen Jahren MD5, welcher wie der RSA-Algorithmus
aus der Feder von Ron Rivest stammt. Nachdem jedoch einige Angrie bekannt geworden sind, wird
mittlerweile davon abgeraten, MD5 für neue Signaturen einzusetzen. Ein alternatives, von der NSA mitentwickeltes Verfahren, SHA-1, hat in der Zwischenzeit viel Boden gut gemacht. Jedoch sind mittlerweile
auch an SHA-1 Schwachstellen entdeckt worden. Die Nachfolge-Algorithmen SHA-256 und SHA-512 haben bislang keine Schwachstellen aufgezeigt.
Natürlich lassen sich Verschlüsselung und Signatur auch kombinieren, um die Daten vor fremden
Blicken zu schützen und die Integrität zu gewährleisten. Hierbei ist es jedoch nicht unerheblich, ob zuerst
verschlüsselt und dann signiert wird oder umgekehrt! Normalerweise wird stets zunächst signiert und dann
verschlüsselt, denn dann ist eine bewusste Veränderung der Daten durch einen Angreifer ausgeschlossen,
da dieser nicht in der Lage ist, die Daten zu entschlüsseln, um die Unterschrift zu fälschen.
4 JCE & Bouncy Castle
Mittels der JCE (Java Cryptographic Extension) innerhalb der JCA (Java Cryptographic Architecture) hat Sun bereits sehr früh (Java 1.2) den Grundstein zur Nutzung von kryptographischen Funktionen in seiner Klassenbibliothek gelegt. War die JCE anfangs noch als separater Download erhältlich, so
ist sie seit Java 1.4 fester Bestandteil des J2SDK. Die JCE gliedert sich nahtlos in das JCF, das Java
Component Framework, ein. Die tatsächliche Umsetzung der kryptographichen Funktionen wird durch
sog. Provider erbracht. Sun liefert einen nativen Provider für alle gängigen Kryptographie-Szenarien
mit, der Entwickler kann aber auch zusätzliche Provider in Form von Plugins nachrüsten.
Ein solcher Provider ist z.B. die Bouncy-Castle (BC) Klassenbibliothek, um die es in diesem Artikel
gehen soll. Diese Provider können sich z.B. in der Anzahl der unterstützen Verschlüsselungsalgorithmen
5
Abbildung 4: Der JCE Provider Selektionsmechanismus
unterscheiden, in der maximal möglichen Schlüssellänge usw. Der Entwickler kann sich beim Nutzen der
kryptographischen Funktionen entscheiden, welchen Provider er verwenden möchte. Wird keine Vorselektion des Providers durch den Entwickler getroen, so kümmert sich die JCE um die Auswahl eines
Providers [Sun05]. Sun liefert mit dem JDK bereits eine ganze Reihe von spezialisierten Providern mit,
u.a. auch einen Wrapper für den Zugri auf die nativen Windows-CryptoAPI-Funktionen.
Die Wahl des Provider-Konzeptes bringt eine Reihe von Vorteilen mit sich:
• Anwendungen sind nicht fest an einen Provider gebunden. Provider können (in den meisten Fällen)
transparent ausgetauscht werden, ohne dass die nutzende Applikation verändert werden muss.
• Der Entwickler befasst sich nicht mit der eigentlichen Implementierung der kryptographischen Verfahren, sondern kann sich auf deren Nutzung innerhalb seiner Applikation konzentrieren.
• Neue oder proprietäre Algorithmen können einfach nachgerüstet werden.
Ein Mischen verschiedener Provider innerhalb einer Anwendung ist zwar möglich, es wird jedoch davon
abgeraten. Um die Beispiele im weiteren Verlauf dieses Artikels nachvollziehen zu können, müssen die BC
Klassenbibliothek und die umlimited strength Policy-Files installiert werden. Eine Anleitung hierfür
ndet sich in [Dau09].
5 Die wichtigsten Klassen für die symmetrische Verschlüsselung
Alle mit der Java-Standardbibliothek ausgelieferten Klassen und Interfaces liegen im Paket javax.crypto
und in dessen Unterpaketen. Aus diesem Paket werden die folgenden Klassen kurz vorgestellt:
• javax.crypto.Cipher
• javax.crypto.spec.IvParameterSpec
• javax.crypto.spec.SecretKeySpec
• javax.crypto.spec.PBEKeySpec
• javax.crypto.SecretKeyFactory
• javax.crypto.KeyGenerator
6
5.1 Die Cipher-Klasse
Mit Hilfe von Methoden der Cipher-Klasse können Daten verschlüsselt werden. Egal welches Verfahren
und welche Schlüssellänge verwendet werden soll, es werden stets die selben vier Methoden verwendet.
Von allen Methoden gibt es diverse Überladungen.
• public static Cipher Cipher.getInstance(String algo, String provider)
Über diesen Methodenaufruf wird der Verschlüsselungsalgorithmus, der Modus, das Padding und der
Provider ausgewählt. algo ist hierbei ein String wie z.B. DES/CBC/PKCS5Padding, welcher in
diesem Fall ein Objekt zurückliefert, mit dem Daten via DES im CBC Modus verschlüsselt werden
können. Das Padding wird gemäÿ PKCS#5 durchgeführt. provider deniert den zu nutzenden
Provider. Für unsere Beispiele ist dies stets BC, um die BC API zu nutzen.
• public void Cipher.init(int mode, Key key)
Mit Hilfe dieser Methode wird der Modus (ENCRYPT_MODE, DECRYPT_MODE) gesetzt und der Key
deniert, der für die Verschlüsselung verwendet werden soll. Die Init-Methode muss stets aufgerufen
worden sein, bevor eine update() oder doFinal()-Operation ausgeführt wird.
• public int Cipher.update(byte[] input, int iOffset, int iLen, byte[] output, int oOffset)
und public int Cipher.doFinal(byte[] input, int iOffset, int iLen, byte[] output, int oOffset)
Hiermit wird die Verschlüsselung des Plaintextes input ab dem Oset iOffset für iLen Zeichen
durchgeführt und in den Ausgabe-Puer output ab Index oOffset geschrieben.
Die doFinal()-Methode schlieÿt den Verschlüsselungsvorgang ab. Danach wird die Chire in den
gleichen Zustand zurückversetzt, in dem sie nach dem Aufruf von init() war. Es ist auch möglich,
den gesamten Plaintext mittels eines Aufrufes von doFinal() zu verschlüsseln. Ob segmentweise
(mittels update()) oder alles auf einmal (mittels doFinal()) verschlüsselt wird, wird durch den
Problemfall bestimmt.
5.2 Die IvParameterSpec-Klasse
Die Klasse IvParameterSpec repräsentiert einen Initialisierungs-Vektor (IV) (siehe dazu auch 2.1.1). Bei
der Verschlüsselung kann der IV auch durch das Cipher-Objekt automatisch erzeugt werden. Für die
Entschlüsselung muss der IV aber auf jeden Fall gesichert werden und dem Empfänger übermittelt werden.
Häug wird der IV einfach an die verschlüsselte Nachricht angehängt.
• public IvParameterSpec(byte[] iv)
erzeugt ein IvParameterSpec-Objekt.
• public byte[] getIv()
gibt den IV zurück.
5.3 Die SecretKeySpec-Klasse
Die Klasse SecretKeySpec repräsentiert einen symmetrischen Verschlüsselungsschlüssel. Verschiedene
Crypto-Algorithmen, wie z.B. AES, nutzen nicht direkt einen Schlüssel, sondern leiten aus diesem Schlüssel die tatsächlichen Schlüssel ab. Für AES werden z.B. zehn verschiedene Schlüssel genutzt.
• public SecretKeySpec(byte[] key, String algo)
Der Parameter key gibt hierbei den Schlüssel an, algo den zu verwendenden Algorithmus. Diese
Klasse ist nicht dafür gedacht, das verwendete Passwort für eine Verschlüsselung als byte[] entgegenzunehmen, hierfür ist die Klasse PBEKeySpec vorgesehen. Wie groÿ das übergebene byte[] sein
muss, wird durch die Wahl des Verschlüsselungsalgorithmus vorgegeben. Im Falle von DES muss es
z.B. 8 Byte lang sein. Eine Überprüfung ndet (hier) nicht statt! Es versteht sich von selbst, dass
der angegebene algo im Konstruktor zum Algorithmus des Cipher-Objektes passen muss, damit
die beiden Objekte miteinander harmonieren.
7
• public byte[] getEncoded()
gibt das byte[] zurück, welches den Schlüssel repräsentiert. Dies kann nützlich sein, wenn man den
Schlüssel zufällig hat berechnen lassen. Wurde der Schlüssel z.B. in einer Smartcard oder in einem
Token erstellt (siehe Abschnitt 9), so ist dieser in der Regel nicht exportierbar und getEncoded()
wird null zurückliefern.
5.4 Die PBEKeySpec und SecretKeyFactory Klassen
Die Klasse SecretKeyFactory kann zusammen mit der PBEKeySpec Klasse verwendet werden, um einen
symmetrischen Schlüssel aus einem Passwort abzuleiten. Gemeinhin wird nie das Passwort selbst für die
Verschlüsselung verwendet, sondern der eigentliche Schlüssel immer nach einem festen Algorithmus daraus abgeleitet. Weit verbreitete Algorithmen hierfür sind jene, welche in den Standards PKCS#5[RSA99b]
und PKCS#12[RSA99a] deniert sind (z.B. PBEWithSHAAnd3KeyTripleDES, welches das SHA-Verfahren
und 3DES verwendet, um den Key abzuleiten). Der Entwickler hat in jedem Fall dafür Sorge zu tragen,
dass das genutzte Passwort nicht trivial ist, denn davor besteht kein Schutz.
• public PBEKeySpec(char[] password, byte[] salt, int iterationCount, int keyLength)
erzeugt ein PBEKeySpec-Objekt. Das übergebene Passwort wird hierbei als char[] übergeben und
nicht als String, damit es möglich ist, das Passwort im Speicher wieder explizit zu löschen. Damit
aus demselben Passwort nicht stets in denselben Schlüssel konvertiert wird, wird ein sogenanntes
Salt verwendet.
Unter einem Salt versteht man einen Zufallswert (z.B. Uhrzeit), welcher zusammen mit dem Passwort in die Generierungsfunktion einieÿt, und somit das Ergebnis individualisiert. Dies macht
es möglich, das gleiche Passwort für unterschiedliche Zwecke zu verwenden und dennoch jedesmal
einen anderen Schlüssel zu erhalten. Ein Angreifer kann dann nicht erkennen, ob es sich stets um
das gleiche Ausgangspasswort handelt oder um unterschiedliche Passwörter, die er knacken muss.
Das Salt ist normalerweise so groÿ wie die Hash-Länge des für die Schlüsselaufbereitung verwendeten
Hashalgorithmus. Würde kein Salt verwendet, und ein Passwort aus einem Wörterbuch verwendet,
welches 100 verschiedene Wörter enthält, so muss der Angreifer nur 100 Varianten durchprobieren.
Wird jedoch ein Salt verwendet, von dem man weiÿ, dass es 8 Bit lang ist, so gibt es für jedes dieser
100 Worte jweils noch 256 unterschiedliche Salt-Werte, die auch in Kombination durchprobiert sein
wollen.
Der Algorithmus zur Schlüsselaufbereitung wird iterationCount oft durchlaufen. Hierdurch erhöht
sich der Aufwand zusätzlich für einen Angreifer. keylength letztlich bestimmt, wie lang (in Bit)
der aufbereitete SecretKey werden soll.
Die SecretKeyFactory Klasse verwendet die Vorgaben im PBEKeySpec Objekt, um damit nach
einem vorgegebenen Algorithmus den tatsächlichen Schlüssel zu erzeugen.
• public static SecretKeyFactory getInstance(String factory, String provider)
gibt ein SecretKeyFactory Objekt zurück, welches die übergegebene Factory factory verwendet,
um den Schlüssel abzuleiten. provider ist wie in den anderen Beispielen in diesem Text der BouncyCastle Provider BC.
• public SecretKey generateSecret(PBEKeySpec spec)
gibt den aufbereiteten SecretKey zurück, welcher nun mit dem Cipher-Objekt genutzt werden
kann.
5.5 Die KeyGenerator Klasse
Um symmetrische Schlüssel zu erzeugen, kann auch direkt die KeyGenerator-Klasse verwendet werden.
Wie für JCE üblich, wird auch hier ein Objekt mit getInstance() geholt. Dem getInstance() Aufruf wird hierbei der Algorithmus und der Provider übergeben. Beispiel: KeyGenerator generator =
KeyGenerator.getInstance(AES, BC) erzeugt einen KeyGenerator für AES-Schlüssel.
• public final void init(int keysize)
initialisiert den Key-Generator und gibt eine Schlüssellänge von keysize Bit vor.
8
• public final SecretKey generateKey()
erzeugt einen neuen symmetrischen Schlüssel und gibt ihn als SecretKey Objekt zurück.
Der grundlegende Unterschied zwischen einer KeyFactory und KeyGenerator liegt im Prinzip der
Schlüsselerzeugung. Mit einer KeyFactory wird ein KeySpec-Objekt in ein SecretKey-Objekt und zurück
konvertiert, was häug für den Transport von Schlüsselmaterial verwendet wird. Man besitzt hier also in
der Regel den eigentlichen Schlüssel bereits. Dahingegen ist ein KeyGenerator nur dafür da, einen neuen
Schlüssel aus dem Nichts zu erzeugen. In manchem Fällen (z.B. Password-Based Key-Generation) gibt
es gar keinen Generator, sondern es muss eine Factory verwendet werden.
5.6 Beispiel
Folgendes Code-Beispiel nutzt die soeben vorgestellten Klassen und verschlüsselt den Text Hello World
mit dem Passwort ThePassword1! mit dem 3DES-Algorithmus im CBC Verfahren und entschlüsselt
ihn danach wieder.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
String
String
byte [ ]
byte [ ]
i = " H e l l o World ! " ;
password = " ThePassword1 ! " ;
i n p u t = i . toByteArray ( ) ;
s a l t = new byte [ ] {0 xca , 0 x f e , 0xba , . . . } ; // 16 random Bytes
PBEKeySpec pbeSpec = new PBEKeySpec ( password . toCharArray ( ) , s a l t , 2 0 4 8 ) ; // 2048
rounds
S e c r e t K e y F a c t o r y s k f = S e c r e t K e y F a c t o r y . g e t I n s t a n c e ( " PBEWithSHAAnd3KeyTripleDES " ,
"BC" ) ;
SecretKey sKey = s k f . g e n e r a t e S e c r e t ( pbeSpec ) ;
Cipher c i p h e r = Cipher . g e t I n s t a n c e ( " DESede/CBC/PKCS7Padding " , "BC" ) ;
// e n c r y p t
c i p h e r . i n i t ( Cipher .ENCRYPT_MODE, sKey ) ;
IvParameterSpec i v = c i p h e r . getIV ( ) ; // Save t h e auto −g e n e r a t e d IV f o r l a t e r u s e
byte [ ] c i p h e r T e x t = c i p h e r . d o F i n a l ( i n p u t ) ; // c i p h e r T e x t c o n t a i n s now t h e
e n c r y p t e d data
// t r a n s f e r c i p h e r T e x t t o t h e r e c e i v e r . . .
// d e c r y p t
c i p h e r . i n i t ( Cipher .DECRYPT_MODE, sKey , i v ) ; // We need t o p r o v i d e t h e c o r r e c t IV !
byte [ ] d e c r y p t e d = c i p h e r . d o F i n a l ( c i p h e r T e x t ) ;
System . out . p r i n t l n ( new S t r i n g ( d e c r y p t e d ) ) ;
...
Listing 1: Symmetrische Verschlüsselung mit 3DES
Die Eingabeparameter für die Verschlüsselung müssen in Form von byte[] vorliegen. Als Eingabeparameter werden benötigt: input, der eigentlichen Text, der verschlüsselt werden soll, sowie salt, ein
byte[] mit Zufallswerten, welches für die Key-Erzeugung verwendet wird. Damit man einen nutzbaren
SecretKey erhält, muss man die SecretKeyFactory verwenden. Der Schlüssel soll aus einem Passwort
erzeugt werden (PBEKeySpec), deswegen wird der Generator PBEWithSHAAnd3KeyTripleDES verwendet.
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWithSHAaAnd3KeyTripleDES", "BC");
Durch den Aufruf SecretKey sKey = skf.generateSecret(pbeSpec) erhält man nun die Schlüsselbytes. In Zeile 11 wird ein Cipher-Objekt geholt, welches mit dem 3DES im CBC-Mode und PKCS7Padding verschlüsselt. Es wird ein Padding benötigt, da 3DES eine Blockgröÿe von 16 Bytes vorausetzt, unser Inputtext jedoch weniger als 16 Bytes umfasst.
Wichtigster Schritt vor dem Beginn der Verschlüsselung ist die Initialisierung des Cipher-Objektes
durch den Aufruf der init()-Methode in Zeile 13. Diesem Aufruf gibt man den Modus ENCRYPT_MODE
und den Key mit. Da kein IV übergeben wurde, erzeugt das Cipher-Objekt diesen intern aus einem
SecureRandom-Objekt. Dieser IV muss nun unbedingt gesichert werden und dem Empfänger bekannt
9
gemacht werden, da ohne ihn die Entschlüsselung des 1. Blockes nicht mehr möglich ist. Die eigentliche
Verschlüsselung des Textes erfolgt nun durch den Aufruf cipher.doFinal(input), welcher den byte[]
Input nimmt, diesen mit dem kongurierten Algorithmus verschlüsselt und ein byte[] zurückgibt.
Die Entschlüsselung des Cipher-Textes erfolgt analog, allerdings muss hier beim Aufruf von init()
nun der IV mitgegeben werden. Ansonsten wird eine Exception geworfen, da der 3DES im CBC zwingend einen IV vorausetzt. Des Weiteren muss nun die Konstante Cipher.DECRYPT_MODE im Aufruf von
cipher.init() übergeben werden. In der Praxis wird der IV häug einfach als allererster Block den
verschlüsselten Daten vorangestellt, da er nicht geheim gehalten werden muss.
Soll mit dem AES-Algorithmus verschlüsselt werden, muss eine andere SecretKeyFactory verwendet
werden: PBEwithSHAand128bitAES-CBC-BC. Alternativ kann der Schlüssel auch mit dem empfohlenen
PKCS#5-Verfahren abgeleitet werden:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
S t r i n g password = " ThePassword1 ! " ;
byte [ ] i n p u t = " H e l l o world " . toByteArray ( ) ;
PKCS5S2ParametersGenerator g e n e r a t o r = new PKCS5S2ParametersGenerator ( ) ;
g e n e r a t o r . i n i t ( PBEParametersGenerator . PKCS5PasswordToBytes ( password . toCharArray ( ) )
, s a l t , 2048) ;
ParametersWithIV params = ( ParametersWithIV ) g e n e r a t o r . g e n e r a t e D e r i v e d P a r a m e t e r s
(128 , 128) ;
KeyParameter keyParam = ( KeyParameter ) params . g e t P a r a m e t e r s ( ) ;
SecretKeySpec key = new SecretKeySpec ( keyParam . getKey ( ) , "AES" ) ;
Cipher a e s = Cipher . g e t I n s t a n c e ( "AES/CBC/PKCS5Padding " ) ;
IvParameterSpec i v S p e c = new IvParameterSpec ( params . getIV ( ) ) ;
a e s . i n i t ( Cipher .ENCRYPT_MODE, key , i v S p e c ) ;
aes . doFinal ( input ) ;
...
Listing 2: Schlüsselableitung beim AES
6 Asymmetrische Verschlüsselung von Daten, Hashfunktionen und
Signaturen
Der folgende Abschnitt zeigt, wie ein Hash bzw. Message-Digest erstellt wird, um die Integrität von
Daten zu überprüfen. Auÿerdem werden zwei Verfahrenvorgestellt, um RSA-Schlüssel zu erzeugen. Es
wird gezeigt, wie man damit verschlüsseln und Daten signieren kann.
6.1 MessageDigests
Ein Hash/Digest-Objekt wird durch die Klasse MessageDigest repräsentiert. Es hat ähnliche Methoden
wie das Cipher-Objekt.
6.1.1 Die Klasse MessageDigest
• public static MessageDigest getInstance(String algo, Provider provider)
Diese Klassenmethode liefert ein Digest-Objekt des Typs algo vom Provider provider zurück.
• public void update(byte[] input, int offset, int len)
lässt len Bytes ab Stelle offset aus dem Array input in die Berechnung des Hashs einieÿen.
• byte[] digest()
schlieÿt die Berechnung des Message Digest ab und liefert diesen als byte[] zurück.
• static boolean isEqual(byte[] a, byte[] b)
überprüft zwei übergebene Digest-Werte auf Gleichheit. Die beiden Digests sind normalerweise der
übergebene und der selbst erstellte Digest.
10
Der Hash wird normalerweise verschlüsselt an die zu übertragenden Daten angehängt. Dazu nutzt
man die abschnittsweise Verschlüsselung mit Hilfe der Methode update() des Cipher-Objektes(!). Dies
könnte z.B. wie folgt implementiert werden:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
Cipher c i p h e r = Cipher . g e t I n s t a n c e ( "AES/CTR/ NoPadding " , "BC" ' ) ;
byte [ ] i n p u t = 0 xca , 0 x f e , 0xba , 0 xbe , 0x . . . ; // 32 data b y t e s
Key
key
= ...
IvParameterSpec = . . .
M e s s a g e D i g e s t hash = M e s s a g e D i g e s t . g e t I n s t a n c e ( "SHA− 1" , "BC" ) ;
// e n c r y p t & hash
c i p h e r . i n i t ( Cipher .ENCRYPT_MODE, key , i v S p e c ) ;
byte [ ] c i p h e r T e x t = new byte [ c i p h e r . g e t O u t p u t S i z e ( i n p u t . l e n g t h ) + hash .
getDigestLength () ) ] ;
i n t c tLe ng th = c i p h e r . update ( input , 0 , i n p u t . l e n g t h ( ) , c i p h e r T e x t , 0 ) ;
hash . update ( i n p u t ) ;
ct Le ngt h += c i p h e r . d o F i n a l ( hash . d i g e s t ( ) , 0 , hash . g e t D i g e s t L e n g t h ( ) , c i p h e r T e x t ,
ctLe ng ht ) ;
// d e c r y p t & check hash
c i p h e r . i n i t ( Cipher .DECRYPT_MODE, key , i v S p e c ) ;
byte [ ] pText = c i p h e r . d o F i n a l ( c i p h e r T e x t , 0 , c tLe ng th ) ;
i n t msgLength = pText . l e n g t h − hash . g e t D i g e s t L e n g t h ( ) ;
hash . update ( pText , 0 , msgLength ) ;
byte [ ] msgHash = new byte [ hash . g e t D i g e s t L e n g t h ( ) ] ;
System . a r r a y c o p y ( pText , msgLength , msgHash , 0 , msgHash . l e n g t h ) ;
i f ( ! M e s s a g e D i g e s t . i s E q u a l ( hash . d i g e s t ( ) , msgHash ) {
// e r r o r h a n d l i n g
}
...
Listing 3: Ein Beispiel für einen SHA-1 MessageDigest
6.2 RSA
Natürlich bietet BC auch Klassen an, um Daten mit dem RSA-Verfahren zu verschlüsseln. Die wichtigesten Konstrukte werden im Folgenden vorgestellt.
6.2.1 Die KeyPairGenerator-Klasse
Die Klasse KeyPairGenerator kann genutzt werden, um ein zufälliges RSA-Schlüsselpaar bestehend aus
Private Key und Public Key zu erzeugen.
• public static KeyPairGenerator getInstance(String algo, Provider provider)
Über die statische getInstance()-Methode erhält man das KeyPairGenerator-Objekt, auf dem
man die folgenden Methoden aufrufen kann. Um einen Generator vom Typ RSA zu erhalten, lautet
der Aufruf: KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC").
• public void initialize(int keysize)
Mit Hilfe der initialize-Methode kann vorgegeben werden, wie lang die RSA Schlüssel werden
sollen. In der Praxis sinnvolle Werte sind 1024, 2048 oder 4096.
• public KeyPair generateKeyPair()
erzeugt das RSA Schlüsselpaar und gibt ein KeyPair-Objekt zurück.
11
6.2.2 Die Klasse KeyPair
Die Klasse KeyPair repräsentiert ein Schlüsselpaar. Die beiden public Methoden getPrivate() und
getPublic() geben das jeweilige Schlüsselpaar als PrivateKey bzw. PublicKey zurück.
Ein Beispiel soll die folgende Anwendung verdeutlichen:
1
2
3
4
5
6
7
...
KeyPairGenerator g e n e r a t o r = KeyPairGenerator . g e t I n s t a n c e ( "RSA" , "BC" ) ;
g e n e r a t o r . i n i t i a l i z e ( 1 0 2 4 ) ; // We want 1024 b i t k e y s
KeyPair p a i r = g e n e r a t o r . g e n e r a t e K e y P a i r ( ) ;
PublicKey pubKey = p a i r . g e t P u b l i c ( ) ;
PrivateKey privKey = p a i r . g e t P r i v a t e ( ) ;
...
Listing 4: Erzeugen eines Zufalls-RSA-Schlüsselpaars
6.2.3 Die Klassen RSAPublicKeySpec und RSAPrivateKeySpec
Diese beiden Klassen können zusammen mit einer KeyFactory-Klasse verwendet werden, um RSAPublicKey
und RSAPrivateKey-Objekte anhand vorgegebener Parameter zu erzeugen.
• public RSAPrivateKeySpec(BigInteger modulus, BigInteger privateExponent)
• public RSAPublicKeySpec(BigInteger modulus, BigInteger publicExponent)
Die Konstruktoren der beiden Klassen erzeugen die jeweiligen Schlüsselpaarbeschreibungs-Objekte mit
den übergebenen Generator-Primzahlen modulus bzw. private/publicExponent.
Mit Hilfe der aus dem AES-Beispiel schon bekannten KeyFactory können nun die Schlüssel generiert
werden:
1
2
3
4
5
6
7
...
KeyFactory ke yFac tory = KeyFactory . g e t I n s t a n c e ( "RSA" , "BC" ) ;
RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec ( new B i g I n t e g e r ( "
d 4 6 f 4 7 3 a 2 d 7 4 6 5 3 7 d e 2 0 5 6 a e 3 0 9 2 c 4 5 1 " , 1 6 ) , new B i g I n t e g e r ( " 1 1 " , 1 6 ) ;
RSAPrivateKeySpec pubKeySpec = new RSAPrivateKeySpec ( new B i g I n t e g e r ( "
d 4 6 f 4 7 3 a 2 d 7 4 6 5 3 7 d e 2 0 5 6 a e 3 0 9 2 c 4 5 1 " , 1 6 ) , new B i g I n t e g e r ( " 5 7 7 9 1
d5430d593164082036ad8b29fc1 " , 1 6 ) ;
RSAPublicKey pubKey = ( RSAPublicKey ) k eyFa cto ry . g e n e r a t e P u b l i c ( pubKeySpec ) ;
RSAPrivateKey privKey = ( RSAPrivateKey ) k eyFa ctor y . g e n e r a t e P r i v a t e ( privKeySpec ) ;
...
Listing 5: Erzeugen eines vordenierten RSA-Schlüsselpaars
Nachdem das RSA-Schlüsselpaar erzeugt wurde, wird die bereits bekannte Cipher-Klasse verwendet,
um zu verschlüsseln bzw. zu entschlüsseln.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
byte [ ] i n p u t = new byte [ ] { 0 xca , 0 x f e , 0xba , 0 xbe , . . . } ;
Cipher c i p h e r = Cipher . g e t I n s t a n c e ( "RSA/None/ NoPadding " , "BC" ) ;
KeyPairGenerator g e n e r a t o r = KeyPairGenerator . g e t I n s t a n c e ( "RSA" , "BC" ) ;
g e n e r a t o r . i n i t i a l i z e ( 1 0 2 4 ) ; // We want 1024 b i t k e y s
KeyPair p a i r = g e n e r a t o r . g e n e r a t e K e y P a i r ( ) ;
Key pubKey = p a i r . g e t P u b l i c ( ) ;
Key privKey = p a i r . g e t P r i v a t e ( ) ;
// e n c r y p t
c i p h e r . i n i t ( Cipher .ENCRYPT_MODE, pubKey ) ; // The p u b l i c key i s used f o r e n c r y p t i o n
byte [ ] c i p h e r T e x t = c i p h e r . d o F i n a l ( i n p u t ) ;
// d e c r y p t
12
17
18
19
c i p h e r . i n i t ( Cipher .DECRYPT_MODE, privKey ) ; // The p r i v a t e key i s used f o r
decryption
byte [ ] p l a i n T e x t = c i p h e r . d o F i n a l ( c i p h e r T e x t ) ;
...
Listing 6: Verschlüsseln und Entschlüsseln mit RSA
Testet man das Beispiel mit richtigen Daten durch, so sieht man, dass der cipherText um ein Vielfaches länger ist, als der ursprüngliche plainText. Dies und die rechenintensiven Operationen sind der
Preis, den man sich durch dieses sichere Verfahren erkauft.
7 ASN.1, Zertikate und Keystores
Im Umfeld der Kryptographie wird man häug auch mit dem Begri ASN.1 konfrontiert werden. Hierbei
handelt es sich um eine Notation, in der kryptographische Protokolle dargestellt werden. Hierdurch lässt
sich exakt festlegen, wie z.B. ein RSA Public Key serialisiert wird, bzw. aus welchen Komponenten er
besteht. Zertikate werden ebenfalls in ASN.1 codiert und dann im DER (binär) oder PEM (Text) Format
gespeichert.
Die Struktur für einen RSA Public Key ist beispielsweise wie folgt deniert:
RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
Eine detaillierte Vorstellung von ASN.1 würde hier zu weit führen.
Was sind Zertikate? Ein Zertikat ist eine Urkunde, die einen bestimmten Umstand belegt, wie z.B.
eine bestandene Prüfung. In der Private/PublicKeyKryptographie werden Zertikate verwendet, um PublicKeys einem bestimmten Individuum zuzuordnen
(vergleichbar mit einem digitalen Personalausweis).
Hierbei kann es sich um eine Person (z.B. Student → RBG-Zertikate für Grundstudiums-Tool) oder
auch um Zertikate für Websites handeln (Onlinestore → Zertikat für https://www.onlineshop.de).
Wann immer man eine Webseite über das httpsProtokoll ansurft, werden im Hintergrund Zertikate zwischen Webserver und Client ausgetauscht. Zertikate werden heute normalerweise nach dem X509Standard ausgegeben, und haben damit ein bestimmtes
Set von Eigenschaften und Muss-Feldern. Hierzu zählen
neben dem PublicKey, der in dem Zertikat hinterlegt
ist, u.a. noch der Gültigkeitszeitraum und der Verwendungszweck (KeyUsages, z.B. nur für Signatur oder
Verschlüsselung einzusetzen). Hierbei gibt es sehr viele
feingranulare Abstufungen. Ein Zertikat kann seinen
Zweck, zu belegen, dass ein bestimmter PublicKey einer
gewissen Person zugeordnet ist, nur erfüllen, wenn dieAbbildung 5: Ein X509 Zertikat der RBG
ser Fakt von jemandem attestiert wird, dem der Empfänger des Zertikates vertraut. Hierbei handelt es sich um den Issuer, also den Herausgeber des Zertikates. Im Falle der in.tum-Zertikate ist dies z.B. die RBG). Ein Herausgeber eines Zertikates wird
häug auch CA genannt, Certicate Authority. Die Daten in einem Zertikat werden mit dem PrivateKey einer CA signiert und dem Zertikat beigefügt. Der Empfänger kann dann mittels des PublicKey
des CA-Zertikates diese Signatur überprüfen. (Viele bekannte CA-Zertikate sind normalerweise im
Zertikats-Store (s.u.) bereits durch den OS-Hersteller hinterlegt und können durch den Anwender auch
erweitert werden.) CAs können auch Kaskaden bilden. So kann es z.B. eine sog. Root-CA für die TUM
an sich geben, die dann wiederum Zertikate der einzelnen Fakultäten signiert (→ Intermediate bzw.
Issuing-CAs), welche dann wiederum Enduser-Zertikate ausstellen. Bei der so entstehenden ZertikatsKette spricht man von einer Chain of trust .
13
CAs können Zertikate auch widerrufen. Diesen Vorgang nennt man Revozierung. Dies ist
z.B. notwendig, wenn der PrivateKey kompromittiert wurde oder das Zertikat anderweitig nicht
mehr genutzt werden soll. Die revozierten Zertikate werden in einer CRL, einer Certicate
Revocation List, erfasst. Eine Client-Anwendung,
die mit Zertikaten arbeitet, sollte diese CRL sowie die KeyUsages überprüfen, bevor es ein Zertikat als gültig akzeptiert.
Zertikate werden in sogenannten Certicate
Stores verwaltet. Von Java aus kann man mit
Hilfe der entsprechenden Provider z.B. auf den
Windows-eigenen Store zugreifen, auf einen Store in z.B. einer Smartcard oder den nativen JavaKeystore (JKS) verwenden. Das folgende ProAbbildung 6: Der Zertizierungsprozess
gramm gibt alle Zertikate im Persönlichen Zertikatsstore unter Windows aus.
Als Provider muss der SunMSCAPI-Provider gewählt werden, der die Schnittstelle zum MicrosoftStore bildet. Würde man hier den Sun-Keystore verwenden wollen, wird der Provider JKS verwendet.
Den Keystore önet man mit dem Befehl KeyStore ks = KeyStore.getInstance("Windows-MY") und
betrachten mittels eines Enumerators jeden Eintrag. Mit Hilfe von getCertificate() holt man sich die
Referenz auf das Zertikat und geben uns von dem erhaltenen X509Certificate-Objekt einige Properties
aus.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import
import
import
import
import
java . io . ∗ ;
java . s e c u r i t y . ∗ ;
java . s e c u r i t y . cert . X509Certificate ;
j a v a . u t i l . Enumeration ;
o r g . b o u n c y c a s t l e . o p e n s s l . PEMWriter ;
p u b l i c c l a s s MSCryptAPIAccess {
private s t a t i c Provider p = null ;
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) throws E x c e p t i o n {
P r o v i d e r p = S e c u r i t y . g e t P r o v i d e r ( "SunMSCAPI" ) ;
}
KeyStore ks = KeyStore . g e t I n s t a n c e ( " Windows−MY" ) ;
ks . l o a d ( n u l l , n u l l ) ;
System . out . p r i n t l n ( " K e y s t o r e c o n t a i n s " + ks . s i z e ( ) + " e n t r i e s . " ) ;
Enumeration<S t r i n g > enumerator = ks . a l i a s e s ( ) ;
w h i l e ( enumerator . hasMoreElements ( ) ) {
S t r i n g a l i a s = enumerator . nextElement ( ) ;
System . out . p r i n t ( " Entry : \"" + a l i a s + " \ " " ) ;
i f ( ks . i s C e r t i f i c a t e E n t r y ( a l i a s ) ) {
System . out . p r i n t l n ( " i s a c e r t i f i c a t e ! " ) ;
}
i f ( ks . isKeyEntry ( a l i a s ) ) {
System . out . p r i n t l n ( " i s a key e n t r y " ) ;
}
X 5 0 9 C e r t i f i c a t e c e r t = ( X 5 0 9 C e r t i f i c a t e ) ks . g e t C e r t i f i c a t e ( a l i a s ) ;
printCertInfo ( cert ) ;
}
p u b l i c s t a t i c v o i d p r i n t C e r t I n f o ( X 5 0 9 C e r t i f i c a t e c e r t ) throws E x c e p t i o n {
System . out . p r i n t l n ( " Some i n f o : " ) ;
System . out . p r i n t l n ( " S u b j e c t : " + c e r t . getSubjectDN ( ) . getName ( ) ) ;
System . out . p r i n t l n ( " I s s u e r : " + c e r t . g e t I s s u e r D N ( ) . getName ( ) ) ;
14
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
System . out . p r i n t l n ( " V a l i d from : " + c e r t . g e t N o t B e f o r e ( ) ) ;
System . out . p r i n t l n ( " V a l i d u n t i l : " + c e r t . g e t N o t A f t e r ( ) ) ;
b o o l e a n [ ] u s a g e s = c e r t . getKeyUsage ( ) ;
i f ( u s a g e s != n u l l ) {
System . out . p r i n t ( " KeyUsages : " ) ;
i f ( u s a g e s [ 3 ] ) System . out . p r i n t ( " Data Encipherment , " ) ;
i f ( u s a g e s [ 0 ] ) System . out . p r i n t ( " Data Encipherment , " ) ;
System . out . p r i n t l n ( " " ) ;
}
System . out . p r i n t l n ( " P u b l i c key : " + c e r t . g e t P u b l i c K e y ( ) ) ;
System . out . p r i n t l n ( " C e r t i f i c a t e dump : " ) ;
}
}
ByteArrayOutputStream bOut = new ByteArrayOutputStream ( ) ;
PEMWriter pemWriter = new PEMWriter ( new OutputStreamWriter ( bOut ) ) ;
pemWriter . w r i t e O b j e c t ( c e r t ) ;
pemWriter . c l o s e ( ) ;
System . out . p r i n t l n ( bOut ) ;
Listing 7: Ausgabe der Zertikate im Windows-Zertikatsstore
Mit den Zeilen 49-53 wird das Zertikat in seiner base64-codierte ASN.1 Repräsentation ausgegeben.
Speichert man den ausgegebenen Text, kann man auf diese Weise z.B. ein Zertikat exportieren (was
auch mit Mitteln des Betriebssystems möglich ist).
Ein Zertikat lässt sich auch selbst mit denierbaren Inhalten generieren. Hierfür bietet BC die Klasse
X509V3CertificateGenerator an. Das folgende Listing zeigt exemplarisch deren Verwendung:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
// C r e a t e a p r i v a t e / p u b l i c k e y p a i r
KeyPairGenerator kpg = KeyPairGenerator . g e t I n s t a n c e ( "RSA" , "BC" ) ;
kpg . i n i t i a l i z e ( 1 0 2 4 ) ;
KeyPair kp = kpg . g e n e r a t e K e y P a i r ( ) ;
X 5 0 9 V 3 C e r t i f i c a t e G e n e r a t o r certGen = new X 5 0 9 V 3 C e r t i f i c a t e G e n e r a t o r ( ) ;
// S e t some p r o p e r t i e s
certGen . s e t S e r i a l N u m b e r ( B i g I n t e g e r . v a l u e O f ( 1 ) ) ;
certGen . s e t I s s u e r D N ( new X 5 0 0 P r i n c i p a l ( "CN=Test c e r t i f i c a t e " ) ) ;
certGen . s e t N o t B e f o r e ( new Date ( ) ) ;
// This c e r t w i l l be v a l i d f o r one day o n l y
certGen . s e t N o t A f t e r ( new Date ( System . c u r r e n t T i m e M i l l i s ( ) + 8 6 4 0 0 ∗ 1 0 0 0 ) ) ;
certGen . setSubjectDN ( new X 5 0 0 P r i n c i p a l ( "CN=Test C e r t i f i c a t e " ) ) ;
certGen . s e t P u b l i c K e y ( kp . g e t P u b l i c ( ) ) ;
certGen . s e t S i g n a t u r e A l g o r i t h m ( " SHA1WithRSAEncryption " ) ;
// We want t o u s e t h i s c e r t f o r s i g n a t u r e s and key e n c i p h e r m e n t
certGen . addExtension ( X509Extensions . KeyUsage , t r u e ,
new KeyUsage ( KeyUsage . d i g i t a l S i g n a t u r e | KeyUsage . keyEncipherment ) ) ;
X 5 0 9 C e r t i f i c a t e c e r t = certGen . g e n e r a t e ( kp . g e t P r i v a t e ( ) , "BC" ) ;
...
Listing 8: Erzeugen eines self-signed X509V3 Zertikats mit BC
8 Signierter Code
Zertikate können auch verwendet werden, um JAR-Dateien zu signieren. Einzelne Klassen lassen sich
leider nicht signieren. Dadurch können Empfänger der JARs verizieren, dass der Code während der
Übertragung nicht verändert wurde, bzw. tatsächlich von dem jeweiligen Entwickler stammt. Sun liefert
hierzu das Tool jarsigner mit, welches einfach zu benutzen ist:
jarsigner -verbose -keystore TUM.ks TestJAR.jar markus
15
Enter Passphrase for keystore:
updating: META-INF/MARKUS_D.SF
updating: META-INF/MARKUS_D.RSA
signing: chapter1/eTokenAccess.class
signing: chapter1/MSCryptAPIAccess.class
signing: chapter1/GenerateCertificate.class
signing: .classpath
signing: .project
signing: keytool_list_etoken
signing: resources/eToken.cfg
Der Empfänger kann nun einfach die Integrität überprüfen, indem er einen Verify-Aufruf durchführt.
Hierbei muss das Zertikat nicht zwangsläug im Keystore enthalten sein:
jarsigner -verbose -keystore TUM.ks TestJAR.jar
1014
1181
5341
11040
3692
3991
645
391
153
115
smk
smk
smk
smk
smk
smk
smk
s
m
k
i
=
=
=
=
Mon
Mon
Mon
Sun
Mon
Mon
Sun
Tue
Mon
Sun
Apr
Apr
Apr
Apr
Apr
Apr
Apr
Feb
Apr
Apr
20
20
20
19
20
20
19
17
20
19
20:16:02
20:18:52
20:18:52
20:58:26
17:08:58
17:35:24
20:58:06
20:20:46
19:49:30
11:40:52
CEST 2009 META-INF/MANIFEST.MF
CEST 2009 META-INF/MARKUS_D.SF
CEST 2009 META-INF/MARKUS_D.RSA
CEST 2009 chapter1/eTokenAccess.class
CEST 2009 chapter1/MSCryptAPIAccess.class
CEST 2009 chapter1/GenerateCertificate.class
CEST 2009 .classpath
CET 2009 .project
CEST 2009 keytool_list_etoken
CEST 2009 resources/eToken.cfg
signature was verified
entry is listed in manifest
at least one certificate was found in keystore
at least one certificate was found in identity scope
jar verified.
Anwendung ndet dieses Konzept in Zusammenhang mit dem SecurityManager bzw. dem ClassLoader,
welcher abhängig von der Signatur den geladenen Klassen bestimmte Rechte einräumt oder verweigert.
So lässt sich beispielsweise in einer Security-Policy festlegen, dass alle Klassen eines Applets, die von
Markus signiert sind und von der URL in.tum.de heruntergeladen werden, Schreibrechte im lokalen
Dateisystem erhalten, was ansonsten für ein Applet nicht möglich wäre.
Die Überprüfungen, ob ein bestimmter API Aufruf dem Code erlaubt wird oder nicht, wird durch
den SecurityManager geregelt. In sehr vielen JDK-Klassen (vor allem im Bereich I/O) sind Aufrufe des
SecurityManagers eingewoben.
Beispiel: Der Sourcecode der Methode canRead() in der Klasse File des JDK.
1
2
3
4
5
6
7
p u b l i c b o o l e a n canRead ( ) {
S e c u r i t y M a n a g e r s e c u r i t y = System . g e t S e c u r i t y M a n a g e r ( ) ;
i f ( s e c u r i t y != n u l l ) {
s e c u r i t y . checkRead ( path ) ;
}
r e t u r n f s . c h e c k A c c e s s ( t h i s , F i l e S y s t e m .ACCESS_READ) ;
}
Ist ein SecurityManager für die Anwendung konguriert, so wird anhand der Policy geprüft, ob der
Zugri erlaubt ist. Ist er nicht gestattet, wird eine SecurityException geworfen. Die Policy ist unter
anderem im JRE-Homeverzeichnis in der Datei lib\security\java.policy deniert.
In dieser Datei können sehr feingranulare Permissions auf einzelnen Klassen und Aktionen für verschiedene Besitzer bzw. Quellen gesetzt werden. Auch eigene Permissions können bei Bedarf deniert
werden. Einige Beispiele:
Erlaubt das Lesen unter /tmp und das Verbinden eines Netzsockets für Code, der von java.sun.com
stammt und von Doe John signiert wurde:
16
grant codeBase "http://java.sun.com/*", signedBy "Doe John" {
permission java.io.FilePermission "/tmp/*", "read";
permission java.io.SocketPermission "*", "connect";
};
Erteilt Lese- und Schreibrechte auf /home/alice für die Identität Alice, die über ein X509 Zertikat
repräsentiert wird:
grant principal javax.security.auth.x500.X500Principal "cn=Alice" {
permission java.io.FilePermission "/home/alice", "read, write";
};
Wichtig zu erwähnen ist, dass in einer Java-Applikation normalerweise gar kein SecurityManager geladen ist, d.h. diese Überprüfungen nden bei interaktiv gestarteten Java-Applikationen normalerweise
nicht statt! Anders sieht es bei Applets in einem Browser aus: Hier wird normalerweise automatisch ein
SecurityManager geladen, der u.a. dafür sorgt, dass ein Applet nicht auf die lokale Platte zugreifen kann
und verhindert, dass es sich ins Netz zu einer anderen Ressource verbinden kann, als zu der, von der es
geladen wurde. Möchte man in einer interaktiv gestarteten Java-Applikation auch einen SecurityManager
nutzen, so geschieht dies durch Angabe der Option -Djava.security.manager beim Aufruf. Ein Beispiel:
java -Djava.security.manager TrojanHorse
Neben dem Überprüfen von Security-Policies durch den SecurityManager sorgen auch noch der ClassLoader bzw. der ByteCode-Verier dafür, dass kein bösartiger ByteCode in der JVM ausgeführt wird.
Abbildung 7: Schematische Darstellung der Überprüfungen beim Laden von Java-Code
9 Ausblick
9.1 Starke Kryptographie, Smartcards, Tokens, HSMs
Alle in diesem Artikel vorgestellten Verfahren verwenden ein secret zur Verschlüsselung der Daten, sei es
ein Passwort oder einen Private Key.
Letztendlich hängt die Gesamtsicherheit der Verschlüsselung zu
einem nicht unwesentlichen Teil von der Geheimhaltung eben jenes
Schlüssels ab. Liegt die Schlüsseldatei lediglich auf der Festplatte des
Anwenders, so kann diese durch Schadsoftware wie Trojaner oder Viren
manipuliert werden oder sie wird einfach kopiert. Abhilfe schaen hier
Smartcards oder Tokens, die beide eng verwandt sind: Während eine
Smartcard in der Regel wie eine EC-Karte mit Geld-Chip aussieht und
ein separates Lesegerät (den Smartcard Reader) benötigt, ist ein Token
eine all in one Lösung, die Smartcard und Leser in einem Gerät kombiniert und wie ein USB-Memory Stick aussieht. Eines der am weitesten
verbreiteten Token ist das Aladdin e-Token [eTo]. Ein Token bietet die
Möglichkeit, Schlüssel zu erzeugen, die das Token aber nicht verlassen Abbildung 8: Aladdin eToken
können. Alle kryptographischen Operationen mit diesem Schlüssel wer- Pro 64K
den dann nur innerhalb des Tokens durchgeführt und sind dadurch vor
der Einussnahme durch Schadcode geschützt.
17
Beiden Produkten ist gemein, dass sie eine sogenannte 2-Faktor-Authentizierung unterstützen, indem
nun something you know (das Passwort) nicht mehr ausreicht, sondern nun eine zweite Komponente
notwendig ist (der Besitz des Tokens/der Smartcard), um eine Operation durchzuführen.
Eine nochmals erhöhte Sicherheit kann durch eine 3-Faktor-Authentizierung erreicht werden: Something you have, Something you know, Something you are. Something you are sind dann biometrische
Merkmale wie ein Fingerabdruck, ein Retina-Scan oder ein digitaler Voiceprint.
Für High-End-Security Anforderungen, besonders im Banken und CA Umfeld, kommen sogenannte
HSMs, Hardware Security Modules, zum Einsatz. Hierbei handelt es sich um groÿe Smartcards in der
Form von PCI Steckboards oder kompletten 19" Appliances, welche zusätzlich besonders gegen physische
Angrie geschützt sind und diverse Zertizierungen besitzen (z.B. FIPS, CC): Im Falle eines Angris
wird innerhalb von Millisekunden der Speicher mit dem Schlüsselmaterial gelöscht und ist dadurch für
einen Angreifer wertlos. HSMs haben neben einem schnellen Krypto-Prozessor normalerweise auch einen
echten Zufallszahlengenerator. Für ein Beispiel siehe [Uti].
10 Beschreibung des Demo-Projekts
Bezogen auf diesen Artikel wurde auch ein Demo-Projekt erstellt. Es handelt sich dabei um eine JavaApplikation, die in erster Linie die Funktionsweise verschiedener Kryptographie-Algorithmen demonstriert. Der Anwender kann zufällige oder vordenierte Schlüssel verwenden, um beliebige Texte zu verschlüsseln. Gegebenenfalls kann dabei zwischen verschiedenen Betriebsmodi gewählt werden. Zur Verschlüsselung wird die im Rahmen des Artikels vorgestellt API Bouncy Castle verwendet. Ein eigener
Menüpunkt gibt die durch BC unterstützten Kryptographie-Capabilities aus.
Die Anwendung ist in der Lage, installierte Zertikate auszulesen und anzuzeigen. Darüber hinaus
wird die Authentizierung mittels eines Hardware-Tokens demonstriert.
11 Related Work
In der Kürze dieses Artikels ist es nicht möglich, alle zugrunde liegenden Konzepte aus den betrachteten Themen detailliert vorzustellen. Unverzichtbare Grundkonzepte werden erläutert, für die wissenschaftlichen Hintergründe und Erläuterungen zu Algorithmen, Konzepten und Umsetzungen sei auf die
einschlägige Literatur [Sch07], [Sch96], [Sch03], [And08] und [Sin01] verwiesen.
Literatur
[And08] Ross J. Anderson. Security Engineering. Wiley & Sons, 2008.
[Dau09] Markus
Dauberschmidt.
Installing
the
http://home.in.tum.de/~daubersc/java/InstallGuide.pdf, 2009.
Bouncy
Castle
API.
[DH76] Whiteld Die and Martin E. Hellman. New directions in cryptography. IEEE Transactinos
on Information Theory, 6/1976, 1976.
[eTo] eToken
PRO
-
Portable
USB
Token
with
advanced
smart
card
ftp://ftp.aladdin.com/pub/marketing/eToken/Factsheets/FS_eToken_PRO.pdf.
technology.
[Lau09] Oliver Lau. Faites vos jeux! - Zufallszahlen erzeugen, erkennen und anwenden. c't Magazin
für Computer Technik, 2/09, 2009.
[rsa] RSA explained. http://sergematovic.tripod.com/rsa1.html.
[RSA78] Ron Rivest, Adi Shamir, and Leonard Adleman. A method for obtaining digital signatures
and public-key cryptosystems. Communications of the ACM, Feb 1978, 1978.
[RSA99a] RSA Laboratories. PKCS 12 v1.0: Personal Information Exchange Syntax, 1999.
[RSA99b] RSA Laboratories. PKCS 5 v2.0: Password-Based Cryptography Standard, 1999.
[Sch96] Bruce Schneier. Angewandte Kryptographie. Addison-Wesley, fth edition, 1996.
18
[Sch03] Bruce Schneier. Practical Cryptography. Wiley & Sons, 2003.
[Sch07] Klaus Schmeh. Kryptographie. dpunkt.verlag, third edition, 2007.
[Sin01] Simon Singh. Geheime Botschaften. DTV, 2001.
[Sun05] Sun
Microsystems.
Java
Security
Overview
-
Whitepaper,
2005.
http://java.sun.com/developer/technicalArticles/Security/whitepaper/JS_White_Paper.pdf.
[tru] True hardware random number generators. http://true-random.com/.
[Ulf99] Udo Ulfkotte. Marktplatz der Diebe. Bertelsmann, 1999.
R CryptoServer. http://www.utimaco.de/.
[Uti] Utimaco SafeGuard°
[Zab] Enrique Zabala. Rijndael cipher. http://www.formaestudio.com/rijndaelinspector/.
19