From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Thema dieses Kapitels ist es, die in Java verfügbaren Sicherheitsmechanismen
+vorzustellen. Wir werden dabei zunächst auf allgemeine Konzepte
+aus dem Gebiet der Kryptographie und ihre Implementierung in Java
+eingehen. Anschließend werden die eingebauten Sicherheitsmechanismen
+von Java vorgestellt. Zum Abschluss zeigen wir, wie signierte Applets
+erstellt und verwendet werden, und wie mit ihrer Hilfe eine fein differenzierte
+Sicherheitspolitik etabliert werden kann. Zunächst sollen allerdings
+wichtige Begriffe erläutert werden, die für das Verständnis
+der nachfolgenden Abschnitte von Bedeutung sind.
+
+
+Angenommen, ein Sender will eine Nachricht an einen Empfänger
+übermitteln. Soll das geschehen, ohne dass ein Dritter, dem die
+Nachricht in die Hände fallen könnte, diese entziffern kann,
+könnte sie verschlüsselt
+werden. Der ursprüngliche Nachrichtentext (der als Klartext
+bezeichnet wird) wird dabei mit Hilfe eines dem Sender bekannten Verfahrens
+unkenntlich gemacht. Das als Schlüsseltext
+bezeichnete Ergebnis wird an den Empfänger übermittelt und
+mit Hilfe eines ihm bekannten Verfahrens wieder in den Klartext zurückverwandelt
+(was als entschlüsseln bezeichnet
+wird).
+
+
+
+Abbildung 48.1: Verschlüsseln einer Nachricht
+Solange der Algorithmus zum Entschlüsseln geheim bleibt, ist
+die Nachricht sicher. Selbst wenn sie auf dem Übertragungsweg
+entdeckt wird, kann kein Dritter sie entschlüsseln. Wird das
+Entschlüsselungsverfahren dagegen entdeckt, kann die Nachricht
+(und mit ihr alle anderen Nachrichten, die mit demselben Verfahren
+verschlüsselt wurden) entziffert werden.
+
+
+Um den Schaden durch das Entdecken eines Verschlüsselungsverfahrens
+gering zu halten, werden die Verschlüsselungsalgorithmen in aller
+Regel parametrisiert. Dazu wird beim Verschlüsseln eine als Schlüssel
+bezeichnete Ziffern- oder Zeichenfolge angegeben, mit der die Nachricht
+verschlüssel wird. Der Empfänger benötigt dann zusätzlich
+zur Kenntnis des Verfahrens noch den vom Sender verwendeten Schlüssel,
+um die Nachricht entziffern zu können.
+
+
+Die Wissenschaft, die sich mit dem Verschlüsseln und Entschlüsseln
+von Nachrichten und eng verwandten Themen beschäftigt, wird als
+Kryptographie bezeichnet. Liegt der
+Schwerpunkt mehr auf dem Entschlüsseln, (insbesondere dem Entziffern
+geheimer Botschaften), wird dies als Kryptoanalyse
+bezeichnet. Die Kryptologie schließlich
+bezeichnet den Zweig der Mathematik, der sich mit den formal-mathematischen
+Aspekten der Kryptographie und Kryptoanalyse beschäftigt.
+
+
+
+
+
+Seit dem Altertum sind einfache Verschlüsselungsverfahren bekannt.
+Zu ihnen zählen beispielsweise die Substitutions-Verschlüsselungen,
+bei denen einzelne Buchstaben systematisch durch andere ersetzt werden.
+Angenommen, Klartexte bestehen nur aus den Buchstaben A bis Z, so
+könnte man sie dadurch verschlüsseln, dass jeder Buchstabe
+des Klartextes durch den Buchstaben ersetzt wird, der im Alphabet
+um eine feste Anzahl Zeichen verschoben ist. Als Schlüssel k
+kann beispielsweise die Länge der Verschiebung verwendet werden.
+Ist k beispielsweise 3, so würde jedes A durch ein D,
+jedes B durch ein E, jedes W durch ein Z, jedes X durch ein A usw.
+ersetzt werden.
+
+
+Dieses einfache Verfahren wurde beispielweise bereits von Julius Cäsar
+verwendet, um seinen Generälen geheime Nachrichten zu übermitteln.
+Es wird daher auch als Cäsarische Verschlüsselung
+bezeichnet. Das folgende Listing zeigt eine einfache Implementierung
+dieses Verfahrens, bei dem Schlüssel und Klartext als Argument
+übergeben werden müssen:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 48 - Sicherheit und Kryptographie
+
+
+
+
+
+48.1 Kryptografische Grundlagen
+
+
+
+
+
+
+
+
+48.1.1 Wichtige Begriffe
+
+48.1.2 Einfache Verschlüsselungen
+
+
+
+
+Substitution
+
+
+
+
+Listing 48.1: Verschlüsselung durch Substitution
+
+
+
+
+
+001 /* Listing4801.java */
+002
+003 public class Listing4801
+004 {
+005 public static void main(String[] args)
+006 {
+007 int key = Integer.parseInt(args[0]);
+008 String msg = args[1];
+009 for (int i = 0; i < msg.length(); ++i) {
+010 int c = (msg.charAt(i) - 'A' + key) % 26 + 'A';
+011 System.out.print((char)c);
+012 }
+013 }
+014 }
+
+
+Listing4801.java
+
+Um die Nachricht zu entschlüsseln, verwendet der Empfänger
+dasselbe Verfahren, allerdings mit dem Schlüssel 26 - k:
+
+
+--->java Test2 3 HALLO
+KDOOR
+--->java Test2 23 KDOOR
+HALLO
+
+
+
+
+
+
+
+Ein ähnlich weitverbreitetes Verfahren besteht darin, jedes Zeichen +des Klartexts mit Hilfe des Exklusiv-ODER-Operators mit dem Schlüssel +zu verknüpfen. Durch dessen Anwendung werden alle Bits invertiert, +die zu einem gesetztem Bit im Schlüssel korrespondieren, alle +anderen bleiben unverändert. Das Entschlüsseln erfolgt durch +erneute Anwendung des Verfahrens mit demselben Schlüssel. +
+
![]() |
+
+
+ +Ein Verfahren, bei dem Ver- und Entschlüsselung mit demselben +Algorithmus und Schlüssel durchgeführt werden, wird als +symmetrische Verschlüsselung bezeichnet. |
+
+
|
+![]() |
+
+Eine einfache Implementierung der Exklusiv-ODER-Verschlüsselung +zeigt folgendes Listing: + + +
+
+
+
+001 /* Listing4802.java */
+002
+003 public class Listing4802
+004 {
+005 public static void main(String[] args)
+006 {
+007 int key = Integer.parseInt(args[0]);
+008 String msg = args[1];
+009 for (int i = 0; i < msg.length(); ++i) {
+010 System.out.print((char)(msg.charAt(i) ^ key));
+011 }
+012 }
+013 }
+
+ |
++Listing4802.java | +
+Ein Anwendungsbeispiel könnte so aussehen:
+
+
+--->java Test2 65 hallo
+) --.
+--->java Test2 65 ") --."
+hallo
+
+
+
+
![]() |
+![]() |
+
+
+ +Daß die Rückkonvertierung über die Kommandozeile hier +geklappt hat, liegt daran, dass die Verschlüsselung keine nicht-darstellbaren +Sonderzeichen produziert hat (der Schlüssel 65 kippt lediglich +2 Bits in jedem Zeichen). Im allgemeinen sollte der zu ver- oder entschlüsselnde +Text aus einer Datei gelesen und das Resultat auch wieder in eine +solche geschrieben werden. Dann können alle 256 möglichen +Bitkombinationen je Byte zuverlässig gespeichert und übertragen +werden. |
+
+
|
+![]() |
+
+Derart einfache Verschlüsselungen wie die hier vorgestellten +sind zwar weit verbreitet, denn sie sind einfach zu implementieren. +Leider bieten sie aber nicht die geringste Sicherheit gegen ernsthafte +Krypto-Attacken. Einige der in letzter Zeit bekannt gewordenen (und +für die betroffenen Unternehmen meist peinlichen, wenn nicht +gar kostspieligen) Fälle von Einbrüchen in Softwaressysteme +waren darauf zurückzuführen, dass zu einfache Sicherheitssysteme +verwendet wurden. + +
+Wir wollen diesen einfachen Verfahren nun den Rücken zuwenden, +denn seit dem JDK 1.2 gibt es in Java Möglichkeiten, professionelle +Sicherheitskonzepte zu verwenden. Es ist ein großer Vorteil +der Sprache, dass auf verschiedenen Ebenen Sicherheitsmechanismen +fest eingebaut wurden, und eine missbräuchliche Anwendung der +Sprache erschwert wird. In den folgenden Abschnitten werden wir die +wichtigsten dieser Konzepte vorstellen. +
+
+Ein Message Digest ist eine Funktion, die zu einer gegebenen +Nachricht eine Prüfziffer berechnet. Im Gegensatz zu ähnlichen +Verfahren, die keine kryptografische Anwendung haben (siehe z.B. hashCode +in Abschnitt 8.1.2), muss +ein Message Digest zusätzlich folgende Eigenschaften besitzen: +
+Ein Message Digest wird daher auch als Einweg-Hashfunktion +bezeichnet. Er ist meist 16 oder 20 Byte lang und kann als eine Art +komplizierte mathematische Zusammenfassung der Nachricht angesehen +werden. Message Digests haben Anwendungen im Bereich digitaler Unterschriften +und bei der Authentifizierung. Allgemein gesprochen werden sie dazu +verwendet, sicherzustellen, dass eine Nachricht nicht verändert +wurde. Bevor wir auf diese Anwendungen in den nächsten Abschnitten +zurückkommen, wollen wir uns ihre Implementierung im JDK 1.2 +ansehen. + +
+Praktisch alle wichtigen Sicherheitsfunktionen sind im Paket java.security +oder einem seiner Unterpakete untergebracht. Ein Message Digest wird +durch die Klasse MessageDigest +implementiert. Deren Objekte werden nicht direkt instanziert, sondern +mit der Methode getInstance +erstellt: +
+
+
++public static MessageDigest getInstance(String algorithm) + throws NoSuchAlgorithmException ++ + |
++java.security.MessageDigest | +
+Als Argument wird dabei die Bezeichnung des gewünschten Algorithmus +angegeben. Im JDK 1.2 sind beispielsweise folgende Angaben möglich: +
+Nachdem ein MessageDigest-Objekt +erzeugt wurde, bekommt es die Daten, zu denen die Prüfziffer +berechnet werden soll, in einzelnen Bytes oder Byte-Arrays durch fortgesetzten +Aufruf der Methode update +übergeben: +
+
+
++public void update(byte input) +public void update(byte[] input) +public void update(byte[] input, int offset, int len) ++ + |
++java.security.MessageDigest | +
+Wurden alle Daten übergeben, kann durch Aufruf von digest +das Ergebnis ermittelt werden: +
+
+
++public byte[] digest() ++ + |
++java.security.MessageDigest | +
+Zurückgegeben wird ein Array von 16 bzw. 20 Byte (im Falle anderer +Algorithmen möglicherweise auch andere Längen), in dem der +Message Digest untergebracht ist. Ein Aufruf führt zudem dazu, +dass der Message Digest zurückgesetzt, also auf den Anfangszustand +initialisiert, wird. + +
+Das folgende Listing zeigt, wie ein Message Digest zu einer beliebigen +Datei erstellt wird. Sowohl Algorithmus als auch Dateiname werden +als Kommandozeilenargumente übergeben: + + +
+
+
+
+001 /* Listing4803.java */
+002
+003 import java.io.*;
+004 import java.security.*;
+005
+006 public class Listing4803
+007 {
+008 /**
+009 * Konvertiert ein Byte in einen Hex-String.
+010 */
+011 public static String toHexString(byte b)
+012 {
+013 int value = (b & 0x7F) + (b < 0 ? 128 : 0);
+014 String ret = (value < 16 ? "0" : "");
+015 ret += Integer.toHexString(value).toUpperCase();
+016 return ret;
+017 }
+018
+019 public static void main(String[] args)
+020 {
+021 if (args.length < 2) {
+022 System.out.println(
+023 "Usage: java Listing4803 md-algorithm filename"
+024 );
+025 System.exit(0);
+026 }
+027 try {
+028 //MessageDigest erstellen
+029 MessageDigest md = MessageDigest.getInstance(args[0]);
+030 FileInputStream in = new FileInputStream(args[1]);
+031 int len;
+032 byte[] data = new byte[1024];
+033 while ((len = in.read(data)) > 0) {
+034 //MessageDigest updaten
+035 md.update(data, 0, len);
+036 }
+037 in.close();
+038 //MessageDigest berechnen und ausgeben
+039 byte[] result = md.digest();
+040 for (int i = 0; i < result.length; ++i) {
+041 System.out.print(toHexString(result[i]) + " ");
+042 }
+043 System.out.println();
+044 } catch (Exception e) {
+045 System.err.println(e.toString());
+046 System.exit(1);
+047 }
+048 }
+049 }
+
+ |
++Listing4803.java | +
+
![]() |
+
+
+ +Im Paket java.security +gibt es zwei Klassen, die einen Message Digest mit einem Stream kombinieren. +DigestInputStream +ist ein Eingabe-Stream, der beim Lesen von Bytes parallel deren Message +Digest berechnet; DigestOutputStream +führt diese Funktion beim Schreiben aus. Beide übertragen +die eigentlichen Bytes unverändert und können dazu verwendet +werden, in einer Komposition von Streams »nebenbei« einen +Message Digest zu berechnen. |
+
+
|
+![]() |
+
+Ein wichtiges Anwendungsgebiet von Message Digests ist die Authentifizierung, +d.h. die Überprüfung, ob die Person oder Maschine, mit der +kommuniziert werden soll, tatsächlich »echt« ist (also +die ist, die sie vorgibt zu sein). Eine Variante, bei der ein Anwender +sich mit einem Benutzernamen und Paßwort autorisiert, kann mit +Hilfe eines Message Digests in folgender Weise realisiert werden: +
+Bemerkenswert daran ist, dass das System nicht die Paßwörter +selbst speichert, auch nicht in verschlüsselter Form. Ein Angriff +auf die Benutzerdatenbank mit dem Versuch, gespeicherte Paßwörter +zu entschlüsseln, ist daher nicht möglich. Eine bekannte +(und leider schon oft erfolgreich praktizierte) Methode des Angriffs +besteht allerdings darin, Message Digests zu allen Einträgen +in großen Wörterbüchern berechnen zu lassen, und sie +mit den Einträgen der Benutzerdatenbank zu vergleichen. Das ist +einer der Gründe dafür, weshalb als Paßwörter +niemals Allerweltsnamen oder einfache, in Wörterbüchern +verzeichnete, Begriffe verwendet werden sollten. + + + + +
+Eine weitere Anwendung von Message Digests besteht darin, die Existenz +von Geheimnissen oder den Nachweis der Kenntnis bestimmter Sachverhalte +nachzuweisen, ohne deren Inhalt preiszugeben - selbst nicht eigentlich +vertrauenswürdigen Personen. Dies wird in Bruce Schneier's Buch +als Zero-Knowledge Proof bezeichnet +und funktioniert so: +
+Das Geheimnis ist nicht veröffentlicht, der Nachweis für +seine Existenz zum Zeitpunkt X aber erbracht. Muß A Jahre später +die Existenz dieser Informationen nachweisen, holt es die Diskette +mit dem Geheimnis aus dem Tresor, berechnet den Message Digest erneut +und zeigt dessen Übereinstimmung mit dem seinerzeit in der Zeitung +veröffentlichten. + + + + +
+Eine weitere Anwendung von Message Digests besteht im Erstellen von +Fingerprints (also digitalen Fingerabdrücken) zu öffentlichen +Schlüsseln (was das genau ist, wird in Abschnitt 48.1.5 +erklärt). Um die Korrektheit eines öffentlichen Schlüssels +nachzuweisen, wird daraus ein Message Digest berechnet und als digitaler +Fingerabdruck an prominenter Stelle veröffentlicht (beispielsweise +in den Signaturen der E-Mails des Schlüsselinhabers). + +
+Soll vor der Verwendung eines öffentlichen Schlüssel überprüft +werden, ob dieser auch wirklich dem gewünschten Inhaber gehört, +ist lediglich der (durch das Schlüsselverwaltungsprogramm adhoc +berechnete) Fingerprint des öffentlichen Schlüssels mit +dem in der E-Mail veröffentlichten zu vergleichen. Stimmen beide +überein, erhöht sich das Vertrauen in die Authentizität +des öffentlichen Schlüssels und er kann verwendet werden. +Stimmen sie nicht überein, sollte der Schlüssel auf keinen +Fall verwendet werden. Wir werden in Abschnitt 48.1.7 +noch einmal auf diese Problematik zurückkommen. + + + + +
+Zufallszahlen wurden bereits in Abschnitt 16.1 +vorgestellt. In kryptografischen Anwendungen werden allerdings bessere +Zufallszahlengeneratoren benötigt, als in den meisten Programmiersprachen +implementiert sind. Einerseits sollte die Verteilung der Zufallszahlen +besser sein, andererseits wird eine größere Periodizität +gefordert (das ist die Länge der Zahlensequenz, nach der sich +eine Folge von Zufallszahlen frühestens wiederholt). Zudem muss +die nächste Zahl der Folge praktisch unvorhersagbar sein - selbst +wenn deren Vorgänger bekannt sind. +
+
![]() |
+
+
+ +Es ist bekannt, dass sich mit deterministischen Maschinen (wie Computerprogramme +es beispielsweise sind) keine echten Zufallszahlen erzeugen +lassen. Eigentlich müssten wir daher von Pseudo-Zufallszahlen +sprechen, um darauf hinzuweisen, dass unsere Zufallszahlengeneratoren +stets deterministische Zahlenfolgen erzeugen. Mit der zusätzlichen +Forderung kryptografischer Zufallszahlen, praktisch unvorhersagbare +Zahlenfolgen zu generieren, wird diese Unterscheidung an dieser Stelle +unbedeutend. Tatsächlich besteht der wichtigste Unterschied zu +»echten« Zufallsgeneratoren nur noch darin, dass deren Folgen +nicht zuverlässig reproduziert werden können (was +bei unseren Pseudo-Zufallszahlen sehr wohl der Fall ist). Wir werden +im folgenden daher den Begriff »Zufallszahl« auch dann verwenden, +wenn eigentlich »Pseudo-Zufallszahl« gemeint ist. |
+
+
|
+![]() |
+
+Die Klasse SecureRandom +des Pakets java.security +implementiert einen Generator für kryptografische Zufallszahlen, +der die oben genannten Eigenschaften besitzt. Er wird durch Aufruf +der Methode getInstance +ähnlich instanziert wie ein Message Digest: +
+
+
++public static SecureRandom getInstance(String algorithm) + throws NoSuchAlgorithmException ++ + |
++java.security.MessageDigest | +
+Als Algorithmus ist beispielsweise »SHA1PRNG« im JDK 1.2 +implementiert. Hierbei entstehen die Zufallszahlen aus der Berechnung +eines Message Digests für eine Pseudonachricht, die aus einer +Kombination aus Initialwert und fortlaufendem Zähler besteht. +Die Klasse SecureRandom +stellt weiterhin die Methoden setSeed +und nextBytes +zur Verfügung: +
+
+
++public void setSeed(long seed) + +public void nextBytes(byte[] bytes) ++ + |
++java.security.MessageDigest | +
+Mit setSeed +wird der Zufallszahlengenerator initialisiert. Die Methode sollte +nach der Konstruktion einmal aufgerufen werden, um den Initialwert +festzulegen (andernfalls macht es der Generator selbst). Gleiche Initialwerte +führen auch zu gleichen Folgen von Zufallszahlen. Mit nextBytes +wird eine beliebig lange Folge von Zufallszahlen erzeugt und in dem +als Argument übergebenen Byte-Array zurückgegeben. + +
+Das folgende Listing instanziert einen Zufallszahlengenerator und +erzeugt zehn Folgen zu je acht Bytes Zufallszahlen, die dann auf dem +Bildschirm ausgegeben werden: + + +
+
+
+
+001 /* Listing4804.java */
+002
+003 import java.security.*;
+004
+005 public class Listing4804
+006 {
+007 /**
+008 * Konvertiert ein Byte in einen Hex-String.
+009 */
+010 public static String toHexString(byte b)
+011 {
+012 int value = (b & 0x7F) + (b < 0 ? 128 : 0);
+013 String ret = (value < 16 ? "0" : "");
+014 ret += Integer.toHexString(value).toUpperCase();
+015 return ret;
+016 }
+017
+018 public static void main(String[] args)
+019 {
+020 try {
+021 //Zufallszahlengenerator erstellen
+022 SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
+023 byte[] data = new byte[8];
+024 //Startwert initialisieren
+025 rand.setSeed(0x123456789ABCDEF0L);
+026 for (int i = 0; i < 10; ++i) {
+027 //Zufallszahlen berechnen
+028 rand.nextBytes(data);
+029 //Ausgeben
+030 for (int j = 0; j < 8; ++j) {
+031 System.out.print(toHexString(data[j]) + " ");
+032 }
+033 System.out.println();
+034 }
+035 } catch (Exception e) {
+036 System.err.println(e.toString());
+037 System.exit(1);
+038 }
+039 }
+040 }
+
+ |
++Listing4804.java | +
+Eines der Hauptprobleme bei der Anwendung symmetrischer Verschlüsselungen +ist das der Schlüsselübertragung. Eine verschlüsselte +Nachricht kann nämlich nur dann sicher übertragen werden, +wenn der Schlüssel auf einem sicheren Weg vom Sender zum Empfänger +gelangt. Je nach räumlicher, technischer oder organisatorischer +Distanz zwischen beiden Parteien kann das unter Umständen sehr +schwierig sein. + +
+Mit der Erfindung der Public-Key-Kryptosysteme wurde dieses Problem +Mitte der siebziger Jahre entscheidend entschärft. Bei einem +solchen System wird nicht ein einzelner Schlüssel verwendet, +sondern diese treten immer paarweise auf. Einer der Schlüssel +ist öffentlich und dient dazu, Nachrichten zu verschlüsseln. +Der anderen Schlüssel ist privat. Er dient dazu, mit dem öffentlichen +Schlüssel verschlüsselte Nachrichten zu entschlüsseln. + +
+Das Schlüsselübertragungsproblem wird nun dadurch gelöst, +dass ein potentieller Empfänger verschlüsselter Nachrichten +seinen öffentlichen Schlüssel an allgemein zugänglicher +Stelle publiziert. Seinen privaten Schlüssel hält er dagegen +geheim. Will ein Sender eine geheime Nachricht an den Empfänger +übermitteln, verwendet er dessen allgemein bekannten öffentlichen +Schlüssel und überträgt die verschlüsselte Nachricht +an den Empfänger. Nur mit Hilfe seines privaten Schlüssels +kann dieser nun die Nachricht entziffern. + +
+Das Verfahren funktioniert natürlich nur, wenn der öffentliche +Schlüssel nicht dazu taugt, die mit ihm verschlüsselte Nachricht +zu entschlüsseln. Auch darf es nicht möglich sein, mit vertretbarem +Aufwand den privaten Schlüssel aus dem öffentlichen herzuleiten. +Beide Probleme sind aber gelöst, und es gibt sehr leistungsfähige +und sichere Verschlüsselungsverfahren, die auf dem Prinzip der +Public-Key-Kryptographie beruhen. Bekannte Beispiele für solche +Systeme sind RSA (benannt nach ihren +Erfindern Rivest, Shamir und Adleman) und DSA +(Digital Signature Architecture). + +
+Asymmetrische Kryptosysteme haben meist den Nachteil, sehr viel langsamer +zu arbeiten als symmetrische. In der Praxis kombiniert man daher beide +Verfahren und kommt so zu hybriden Kryptosystemen . +Um eine geheime Nachricht von A nach B zu übertragen, wird dabei +in folgenden Schritten vorgegangen: +
+Fast alle Public-Key-Kryptosysteme arbeiten in dieser Weise als Hybridsysteme. +Andernfalls würde das Ver- und Entschlüsseln bei großen +Nachrichten viel zu lange dauern. Ein bekanntes Beispiel für +ein solches System ist PGP (Pretty +Good Privacy) von Phil Zimmermann. +Es wird vorwiegend beim Versand von E-Mails verwendet und gilt als +sehr sicher. Freie Implementierungen stehen für viele Plattformen +zu Verfügung. +
+
![]() |
+
+
+ +Das Ver- und Entschlüsseln von Daten mit Hilfe von asymmetrischen +Verfahren war bis zur Version 1.3 nicht im JDK enthalten. Zwar gab +es als Erweiterung zum JDK die JCE +(JAVA Cryptography Extension), doch +diese durfte nur in den USA und Kanada verwendet werden. Mit dem JDK +1.4 wurden die JCE, sowie die Java Secure Socket Extension +(JSSE) und der Java Authentication +and Authorization Service (JAAS) +fester Bestandteil des JDK. Dennoch gibt es nach wie vor einige Einschränkungen +in der Leistungsfähigkeit der einzelnen Pakete, die auf US-Exportbeschränkungen +zurückzuführen sind. Details können in der Dokumentation +zum JDK 1.4 oder neueren Versionen nachgelesen werden. |
+
+
|
+![]() |
+
+Ein großer Vorteil der Public-Key-Kryptosysteme ist es, dass +sie Möglichkeiten zum Erstellen und Verifizieren von digitalen +Unterschriften bieten. Eine digitale Unterschrift besitzt folgende +wichtige Eigenschaften: +
+Beide Eigenschaften sind für den elektronischen Datenverkehr +so fundamental wie die Verschlüsselung selbst. Technisch basieren +sie darauf, dass die Funktionsweise eines Public-Key-Kryptosystems +sich umkehren läßt. Daß es also möglich ist, +Nachrichten, die mit einem privaten Schlüssel verschlüsselt +wurden, mit Hilfe des korrespondierenden öffentlichen Schlüssels +zu entschlüsseln. + +
+Im Prinzip funktioniert eine digitale Unterschrift so: + +
+Will A eine Nachricht signieren, so verschlüsselt er sie mit +seinem privaten Schlüssel. Jeder, der im Besitz des öffentlichen +Schlüssel von A ist, kann sie entschlüsseln. Da nur A seinen +eigenen privaten Schlüssel kennt, muss die Nachricht von +ihm stammen. Da es keinem Dritten möglich ist, die entschlüsselte +Nachricht zu modifizieren und sie erneut mit dem privaten Schlüssel +von A zu verschlüsseln, ist auch die Integrität der Nachricht +sichergestellt. Den Vorgang des Überprüfens der Integrität +und Authentizität bezeichnet man als Verifizieren einer +digitalen Unterschrift. + +
+In der Praxis sind die Dinge wieder einmal etwas komplizierter, denn +die Langsamkeit der asymmetrischen Verfahren erfordert eine etwas +aufwändigere Vorgehensweise. Statt die komplette Nachricht zu +verschlüsseln, berechnet A zunächst einen Message Digest +der Nachricht. Diesen verschlüsselt A mit seinem privaten Schlüssel +und versendet ihn als Anhang zusammen mit der Nachricht. Ein Empfänger +wird die Nachricht lesen, ihren Message Digest bilden, und diesen +dann mit dem (mit Hilfe des öffentlichen Schlüssels von +A entschlüsselten) Original-Message-Digest vergleichen. Stimmen +beide überein, ist die Signatur gültig. Die Nachricht stammt +dann sicher von A und wurde nicht verändert. Stimmen sie nicht +überein, wurde sie ver- oder gefälscht. + +
+Das JDK stellt Klassen zum Erzeugen und Verifizieren digitaler Unterschriften +zur Verfügung. Wir wollen uns beide Verfahren in den folgenden +Abschnitten ansehen. Zuvor wird allerdings ein Schlüsselpaar +benötigt, dessen Generierung im nächsten Abschnitt besprochen +wird. + + + + +
+Um digitale Unterschriften erzeugen und verifizieren zu können, +müssen Schlüsselpaare erzeugt und verwaltet werden. Seit +dem JDK 1.2 wird dazu eine Schlüsseldatenbank verwendet, auf +die mit Hilfe des Hilfsprogramms keytool +zugegriffen werden kann. keytool +kann Schlüsselpaare erzeugen, in der Datenbank speichern und +zur Bearbeitung wieder herausgeben. Zudem besitzt es die Fähigkeit, +Zertifikate (siehe Abschnitt 48.1.7) +zu importieren und in der Datenbank zu verwalten. Die Datenbank hat +standardmäßig den Namen ».keystore« +und liegt im Home-Verzeichnis des angemeldeten Benutzers (bzw. im +Verzeichnis \windows eines Windows-95/98-Einzelplatzsystems). + +
+keytool +ist ein kommandozeilenbasiertes Hilfsprogramm, das eine große +Anzahl an Funktionen bietet. Wir wollen hier nur die für den +Umgang mit digitalen Unterschriften benötigten betrachten. Eine +vollständige Beschreibung findet sich in der Tool-Dokumentation +des JDK. +
+
![]() |
+![]() |
+
+
+ +Im JDK 1.1 gab es keytool +noch nicht. Statt dessen wurde das Programm javakey +zur Schlüsselverwaltung verwendet. Hier soll nur das Security-API +des JDK 1.2 und darüber betrachtet werden. Wir wollen daher auf +javakey +und andere Eigenschaften der Prä-1.2-JDKs nicht eingehen. |
+
+
|
+![]() |
+
+Um ein neues Schlüsselpaar zu erzeugen, ist keytool +mit dem Kommando -genkey aufzurufen. +Zusätzlich müssen weitere Parameter angegeben werden: +
+Die Optionen für den Schlüssel- und Signaturtyp (-keyalg +und -sigalg) sowie die Schlüssellänge +(-keysize) und die Gültigkeitsdauer +(-validity) sollen unspezifiziert +bleiben (und daher gemäß den eingebauten Voreinstellungen +belegt werden). Zusätzlich besitzt jede Schlüsseldatenbank +ein Zugriffspaßwort, das mit der Option -storepass +(oder alternativ in der Eingabezeile) angegeben wird. Schließlich +besitzt jeder private Schlüssel ein Schlüsselpaßwort, +das mit der Option -keypass +(oder über die Eingabezeile) angegeben wird. + +
+Wir wollen zunächst ein Schlüsselpaar mit dem Aliasnamen
+»hjp3« erzeugen und mit dem Paßwort »hjp3key«
+vor unberechtigtem Zugriff schützen. Die Schlüsseldatenbank
+wird beim Anlegen des ersten Schlüssel automatisch erzeugt und
+bekommt das Paßwort »hjp3ks« zugewiesen. Wir verwenden
+dazu folgendes Kommando (bitte haben Sie etwas Geduld, das Programm
+benötigt eine ganze Weile):
+
+
+c:\-->keytool -genkey -alias hjp3 -dname
+ "CN=Guido Krueger,O=Computer Books,C=de"
+Enter keystore password: hjp3ks
+Enter key password for <hjp3>
+ (RETURN if same as keystore password): hjp3key
+
+
+
+
+Nun wird ein DSA-Schlüsselpaar der Länge 1024 mit einer
+Gültigkeitsdauer von 90 Tagen erzeugt. Zur Überprüfung
+kann das Kommando -list (in
+Kombination mit -v) angegeben
+werden:
+
+
+C:\--->keytool -alias hjp3 -list -v
+Enter keystore password: hjp3ks
+Alias name: hjp3
+Creation date: Sun Dec 26 17:11:36 GMT+01:00 1999
+Entry type: keyEntry
+Certificate chain length: 1
+Certificate[1]:
+Owner: CN=Guido Krueger, O=Computer Books, C=de
+Issuer: CN=Guido Krueger, O=Computer Books, C=de
+Serial number: 38663e2d
+Valid from: Sun Dec 26 17:11:25 GMT+01:00 1999 until: Sat Mar 25 17:11:25 GMT+01:00 2000
+Certificate fingerprints:
+ MD5: D5:73:AB:06:25:16:7F:36:27:DF:CF:9D:C9:DE:AD:35
+ SHA1: E0:A4:39:65:60:06:48:61:82:5E:8C:47:8A:2B:04:A4:6D:43:56:05
+
+
+
+
+Gleichzeitig wird ein Eigenzertifikat +für den gerade generierten öffentlichen Schlüssel erstellt. +Es kann dazu verwendet werden, digitale Unterschriften zu verifizieren. +Jedes Zertifikat in der Schlüsseldatenbank (und damit jeder eingebettete +öffentliche Schlüssel) gilt im JDK automatisch als vertrauenswürdig. + + + + +
+Wie erwähnt, entsteht eine digitale Unterschrift zu einer Nachricht +durch das Verschlüsseln des Message Digests der Nachricht mit +dem privaten Schlüssel des Unterzeichnenden. Nachdem wir nun +ein Schlüsselpaar erstellt haben, können wir es nun dazu +verwenden, beliebige Dateien zu signieren. + +
+Dazu wird die Klasse Signature +des Pakets java.security +verwendet. Ihre Programmierschnittstelle ähnelt der der Klasse +MessageDigest: +zunächst wird ein Objekt mit Hilfe einer Factory-Methode beschafft, +dann wird es initialisiert, und schließlich werden die Daten +durch wiederholten Aufruf von update +übergeben. Nachdem alle Daten angegeben wurden, berechnet ein +letzter Methodenaufruf das Resultat. + +
+Ein Signature-Objekt +kann wahlweise zum Signieren oder zum Verifizieren verwendet werden. +Welche der beiden Funktionen aktiviert wird, ist nach der Instanzierung +durch den Aufruf einer Initialisierungsmethode festzulegen. Ein Aufruf +von initSign +initialisiert das Objekt zum Signieren, ein Aufruf von initVerify +zum Verifizieren. +
+
+
++public static Signature getInstance(String algorithm) + throws NoSuchAlgorithmException + +public final void initSign(PrivateKey privateKey) + throws InvalidKeyException + +public final void initVerify(PublicKey publicKey) + throws InvalidKeyException ++ + |
++java.security.Signature | +
+
![]() |
+
+
+ +Als Argument von getInstance +wird der gewünschte Signier-Algorithmus übergeben. Auch +hier wird - wie an vielen Stellen im Security-API des JDK - eine Strategie +verfolgt, nach der die verfügbaren Algorithmen konfigurier- und +austauschbar sind. Dazu wurde ein Provider-Konzept +entwickelt, mit dessen Hilfe dem API Klassenpakete zur Verfügung +gestellt werden können, die Funktionalitäten des Security-Pakets +teilweise oder ganz austauschen. Falls der Provider beim Aufruf von +getInstance +nicht angegeben wird, benutzt die Methode den Standard-Provider »SUN«, +der zusammen mit dem JDK ausgeliefert wird. Der zu dem von uns generierten +Schlüssel passende Algorithmus ist »SHA/DSA«. |
+
+
|
+![]() |
+
+Die zum Aufruf der init-Methoden benötigten Schlüssel können +aus der Schlüsseldatenbank beschafft werden. Auf sie kann mit +Hilfe der Klasse KeyStore +des Pakets java.security +zugegriffen werden. Dazu wird zunächst ein KeyStore-Objekt +instanziert und durch Aufruf von load +mit den Daten aus der Schlüsseldatenbank gefüllt. Mit getKey +kann auf einen privaten Schlüssel zugegriffen werden, mit getCertificate +auf einen öffentlichen: +
+
+
++public static KeyStore getInstance(String type) + throws KeyStoreException + +public final Key getKey(String alias, char[] password) + throws KeyStoreException, + NoSuchAlgorithmException, + UnrecoverableKeyException + +public final Certificate getCertificate(String alias) + throws KeyStoreException ++ + |
++java.security.KeyStore | +
+
![]() |
+![]() |
+
+
+ +Das von getCertificate +zurückgegebene Objekt vom Typ Certificate +stammt nicht aus dem Paket java.security, +sondern java.security.cert. +Das in java.security +vorhandene gleichnamige Interface wurde bis zum JDK 1.1 verwendet, +ab 1.2 aber als deprecated +markiert. Wenn nicht mit qualifizierten Klassennamen gearbeitet wird, +muss daher die import-Anweisung +für java.security.cert.Certificate +im Quelltext vor der import-Anweisung +von java.security.Certificate +stehen. |
+
+
|
+![]() |
+
+Die Klasse Certificate +besitzt eine Methode getPublicKey, +mit der auf den im Zertifikat enthaltenen öffentlichen Schlüssel +zugegriffen werden kann: +
+
+
++public PublicKey getPublicKey() ++ + |
++java.security.cert.Certificate | +
+Ist das Signature-Objekt +initialisiert, wird es durch Aufruf von update +mit Daten versorgt. Nachdem alle Daten übergeben wurden, kann +mit sign +die Signatur abgefragt werden. Wurde das Objekt zum Verifizieren initialisiert, +kann das Ergebnis durch Aufruf von verify +abgefragt werden: +
+
+
++public final byte[] sign() + throws SignatureException + +public final boolean verify(byte[] signature) + throws SignatureException ++ + |
++java.security.Signature | +
+Nach diesen Vorüberlegungen können wir uns nun das Programm +zum Erstellen einer digitalen Unterschrift ansehen. Es erwartet zwei +Kommandozeilenargumente: den Namen der zu signierenden Datei und den +Namen der Datei, in den die digitale Unterschrift ausgegeben werden +soll. + + +
+
+
+
+001 /* DigitalSignature.java */
+002
+003 import java.io.*;
+004 import java.security.cert.Certificate;
+005 import java.security.*;
+006
+007 public class DigitalSignature
+008 {
+009 static final String KEYSTORE = "c:\\windows\\.keystore";
+010 static final char[] KSPASS = {'h','j','p','3','k','s'};
+011 static final String ALIAS = "hjp3";
+012 static final char[] KEYPASS = {'h','j','p','3','k','e','y'};
+013
+014 public static void main(String[] args)
+015 {
+016 try {
+017 //Laden der Schlüsseldatenbank
+018 KeyStore ks = KeyStore.getInstance("JKS");
+019 FileInputStream ksin = new FileInputStream(KEYSTORE);
+020 ks.load(ksin, KSPASS);
+021 ksin.close();
+022 //Privaten Schlüssel "hjp3" lesen
+023 Key key = ks.getKey(ALIAS, KEYPASS);
+024 //Signatur-Objekt erstellen
+025 Signature signature = Signature.getInstance("SHA/DSA");
+026 signature.initSign((PrivateKey)key);
+027 //Eingabedatei einlesen
+028 FileInputStream in = new FileInputStream(args[0]);
+029 int len;
+030 byte[] data = new byte[1024];
+031 while ((len = in.read(data)) > 0) {
+032 //Signatur updaten
+033 signature.update(data, 0, len);
+034 }
+035 in.close();
+036 //Signatur berechnen
+037 byte[] result = signature.sign();
+038 //Signatur ausgeben
+039 FileOutputStream out = new FileOutputStream(args[1]);
+040 out.write(result, 0, result.length);
+041 out.close();
+042 } catch (Exception e) {
+043 System.err.println(e.toString());
+044 System.exit(1);
+045 }
+046 }
+047 }
+
+ |
++DigitalSignature.java | +
+Will beispielsweise der Benutzer, dessen privater Schlüssel unter
+dem Aliasnamen »hjp3« in der Schlüsseldatenbank gespeichert
+wurde, die Datei DigitalSignature.java
+signieren und das Ergebnis in der Datei ds1.sign
+abspeichern, so ist das Programm wie folgt aufzurufen:
+
+
+C:\--->java DigitalSignature DigitalSignature.java ds1.sign
+
+
+
+
![]() |
+
+
+ +Die Laufzeit des Programms ist beträchtlich. Das Verschlüsseln +des Message Digest kann auf durchschnittlichen Rechnern durchaus etwa +30 Sekunden dauern. Glücklicherweise ist die Laufzeit nicht nennenswert +von der Dateilänge abhängig, denn das Berechnen des Message +Digests erfolgt sehr schnell. |
+
+
|
+![]() |
+
+Das Programm zum Verifizieren arbeitet ähnlich wie das vorige. +Statt mit initSign +wird das Signature-Objekt +nun mit initVerify +initialisiert und das Ergebnis wird nicht durch Aufruf von sign, +sondern durch Aufruf von verify +ermittelt. + + +
+
+
+
+001 /* VerifySignature.java */
+002
+003 import java.io.*;
+004 import java.security.cert.Certificate;
+005 import java.security.*;
+006
+007 public class VerifySignature
+008 {
+009 static final String KEYSTORE = "c:\\windows\\.keystore";
+010 static final char[] KSPASS = {'h','j','p','3','k','s'};
+011 static final String ALIAS = "hjp3";
+012
+013 public static void main(String[] args)
+014 {
+015 try {
+016 //Laden der Schlüsseldatenbank
+017 KeyStore ks = KeyStore.getInstance("JKS");
+018 FileInputStream ksin = new FileInputStream(KEYSTORE);
+019 ks.load(ksin, KSPASS);
+020 ksin.close();
+021 //Zertifikat "hjp3" lesen
+022 Certificate cert = ks.getCertificate(ALIAS);
+023 //Signature-Objekt erstellen
+024 Signature signature = Signature.getInstance("SHA/DSA");
+025 signature.initVerify(cert.getPublicKey());
+026 //Eingabedatei lesen
+027 FileInputStream in = new FileInputStream(args[0]);
+028 int len;
+029 byte[] data = new byte[1024];
+030 while ((len = in.read(data)) > 0) {
+031 //Signatur updaten
+032 signature.update(data, 0, len);
+033 }
+034 in.close();
+035 //Signaturdatei einlesen
+036 in = new FileInputStream(args[1]);
+037 len = in.read(data);
+038 in.close();
+039 byte[] sign = new byte[len];
+040 System.arraycopy(data, 0, sign, 0, len);
+041 //Signatur ausgeben
+042 boolean result = signature.verify(sign);
+043 System.out.println("verification result: " + result);
+044 } catch (Exception e) {
+045 System.err.println(e.toString());
+046 System.exit(1);
+047 }
+048 }
+049 }
+
+ |
++VerifySignature.java | +
+Soll die Datei DigitalSignature.java
+mit der im vorigen Beispiel erstellten Signatur verifiziert werden,
+kann das durch folgendes Kommando geschehen:
+
+
+C:\--->java VerifySignature DigitalSignature.java ds1.sign
+verification result: true
+
+
+
+
+Wird nur ein einziges Byte in DigitalSignature.java +verändert, ist die Verifikation negativ und das Programm gibt +false aus. Durch eine erfolgreich +verifizierte digitale Unterschrift können wir sicher sein, dass +die Datei nicht verändert wurde. Zudem können wir sicher +sein, dass sie mit dem privaten Schlüssel von »hjp3« +signiert wurde, denn wir haben sie mit dessen öffentlichen Schlüssel +verifiziert. + + + + +
+Ein großes Problem bei der Public-Key-Kryptographie besteht +darin, die Authentizität von öffentlichen Schlüsseln +sicherzustellen. Würde beispielsweise B einen öffentlichen +Schlüssel publizieren, der glaubhaft vorgibt, A zu gehören, +könnte dies zu verschiedenen Unannehmlichkeiten führen: +
+Einen Schutz gegen derartigen Missbrauch bringen Zertifikate. +Ein Zertifikat ist eine Art Echtheitsbeweis für einen öffentlichen +Schlüssel, das damit ähnliche Aufgaben erfüllt wie +ein Personalausweis oder Reisepass. Ein Zertifikat besteht meist aus +folgenden Teilen: +
+Die Glaubwürdigkeit des Zertifikats hängt von der Glaubwürdigkeit +des Ausstellers ab. Wird dieser als vertrauenswürdig angesehen, +d.h. kann man seiner digitialen Unterschrift trauen, so wird man auch +dem Zertifikat trauen und den darin enthaltenen öffentlichen +Schlüssel akzeptieren. + +
+Dieses Vertrauen kann einerseits darauf basieren, dass der Aussteller +eine anerkannte Zertifizierungsautorität ist (auch Certification +Authority, kurz CA, +genannt), deren öffentlicher Schlüssel bekannt und deren +Seriösität institutionell manifestiert ist. Mit anderen +Worten: dessen eigenes Zertifikat in der eigenen Schlüsselverwaltung +bekannt und als vertrauenswürdig deklariert ist. Andererseits +kann das Vertrauen in das Zertifikat daher stammen, dass der Aussteller +persönlich bekannt ist, sein öffentlicher Schlüssel +eindeutig nachgewiesen ist, und seiner Unterschrift Glauben geschenkt +wird. + +
+Der erste Ansatz wird beispielsweise bei X.509-Zertifikaten +verfolgt. Institute, die derartige Zertifikate ausstellen, werden +meist staatlich authorisiert und geprüft. Beispiele dafür +sind VeriSign, Thawte +oder das TA Trustcenter. Der zweite +Ansatz liegt beispielsweise den Zertifikaten in PGP zugrunde. Hier +ist es sogar möglich, öffentliche Schlüssel mit mehreren +digitalen Unterschriften unterschiedlicher Personen zu signieren und +so die Glaubwürdigkeit (bzw. ihre Reichweite) zu erhöhen. + +
+Zertifizierungsinstitute stellen meist auch Schlüsseldatenbanken +zur Verfügung, aus denen Zertifikate abgerufen werden können. +Diese dienen auch als Anlaufstelle, um ungültig gewordene oder +unbrauchbare Zertifikate zu registrieren. Lokale Schlüsselverwaltungen +können sich mit diesen Informationen synchronisieren, um ihren +eigenen Schlüsselbestand up-to-date zu halten. +
| Titel + | Inhalt + | Suchen + | Index + | DOC + | Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 + |
| << + | < + | > + | >> + | API + | © 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de + |