Advanced
Java
Services
|
Digest Authentication |
MD5 Message Digest 5 Algorithmus
Was ist ein Digest
Digest übersetzt das Wörterbuch mit "Auslese" oder "Auswahl", aber auch mit "Auszug" oder
"Kurzfassung" oder "Extrakt". Die letzten Übersetzungen treffen für diesen Fall zu. Ein Digest
ist eine Art Extrakt aus binären Daten, der nach einem mathematischen Algorithmus gebildet wird.
In der Erläuterung zu der abstrakten Klasse java.security.MessageDigest heißt es:
Eine EinwegFunktion ist eine nichtumkehrbare Funktion. Es ist praktisch unmöglich aus dem Digest
wieder die Originaldaten herzustellen. Die bekanntesten Algorithmen zur Erzeugung von Digests tragen
die Abkürzungen "MD5" und "SHA". "MD5" erzeugt aus Binärdaten beliebiger Länge
ein 128 bit bzw. 16 byte langes Muster. Wenn man jedes Byte durch zwei Hexziffern darstellt,
so ergeben 16 byte einen 32 Hexziffern langen String. "SHA" erzeugt aus einem Datum beliebiger
Länge ein 160 bit bzw. 20 byte langes Muster. Stellt man jedes byte wieder mit zwei Hexziffern dar, so
ergeben 20 byte einen 40 Hexziffern langen String. Der MD5-Algorithmus wird in RFC 1321
beschrieben. In der Einleitung zu diesem Dokument heißt es:
Aus der Einleitung zu RFC 1321
Die Klasse java.security.MessageDigest
Mit der Klasse java.security.MessageDigest lassen sich zu beliebigen Daten digests wahlweise
nach dem MD5-Algorithmus oder nach dem SHA-Algorithmus sehr komfortabel erzeugen. Wir brauchen die
Algorithmen nicht selbst implementieren. Über die statischen
import java.security.*:
MessageDigest md5, sha ;
try
{
md5 = MessageDigest.getInstance("md5") ;
sha = MessageDigest.getInstance("sha") ;
}
catch(NoSuchAlgorithmException ex)
{
System.out.println(ex);
}
Berechnung eines Digest zu einem String
Meist sind es ASCII-Texte (Passwörter etc.) zu denen ein Digest ermittelt werden soll. Zuerst müssen die
Daten als Byte-Array vorliegen. Dieses Array übergibt man dann der Methode update() und ruft unmittelbar
danach die parameterlose Methode digest(). Letzere Methode liefert ein byte-Array zurück, indem der Digest
abgelegt worden ist.
String pwd = "wzlbrmpft";
byte[] arr = pwd.getBytes();
md5.update(arr);
byte[] digest = md5.digest();
Die Berechnung des Digest zu "wzlbrmpft" z.Bsp. ergibt folgende Bitfolge:
Die Hexzifferndarstellung dazu sieht folgendermaßen aus:
Diese Hexzifferndarstellung ist für die Digestauthentifizierung wichtig, da der Browser einen Digest
als Hexstring verschickt bzw. erwartet. Leider gibt es keine Methode in der Klasse MessageDigest, die
diesen Hexstring aus einem Digest erzeugt. Es ist allerdings nicht schwer, sich so eine Methode selbst
zu schreiben. Hier eine Möglichkeit, wie so eine Methode aussehen kann.
public static String digest2HexString(byte[] digest)
{
String digestString="";
int low, hi ;
for(int i=0; i < digest.length; i++)
{
low = ( digest[i] & 0x0f ) ;
hi = ( (digest[i] & 0xf0)>>4 ) ;
digestString += Integer.toHexString(hi);
digestString += Integer.toHexString(low);
}
return digestString ;
}
Mit dieser Information können wir uns nun dem eigentlichen Vorgang zuwenden.
Digest Authentication
Der Vorgang besteht im wesentlichen darin, daß sowohl Client wie Server zweimal einen Digest
mit Hilfe eines Paßwortes berechnen. Der Client macht dies mit einem vom Benutzer eingegebenen
Paßwort und schickt diesen Digest und den Benutzernamen zum Server. Der Server kann aus diesem
Digest nicht mehr das Paßwort ermitteln, da die Methode zur Digestberechnung nicht umkehrbar ist.
Mit Hilfe des Benutzernamens kann er jedoch das serverseitig hinterlegte Paßwort aus einer Datenbank
holen und daraus seinerseits einen Digest berechnen. Ist dieser eigene mit dem übermittelten
identisch, so muß es sich um dasselbe Paßwort handeln und dann ist die Authentifizierung gelungen.
(Hier wird natürlich davon Gebrauch gemacht, daß verschiedene Paßwörter auch verschiedene Digest
erzeugen - man lese dazu noch einmal die Einleitung zu RFC 1321 !)
Erhält ein Server eine Anforderung für ein geschütztes Objekt, in der keine Authentifizierung
übermittelt wird und für die eine Digest-Authentifizierung notwendig ist, so antwortet er etwa
folgendermaßen:
Der ResponseHeader, der Digest-Authentifizierung verlangt (kann etwa so aussehen)
HTTP/1.1 401 Unauthorized
Date : Sat, 03 Jan 2004 09:16:46 GMT
Server : Apache/1.3.20 (Linux/SuSE) mod_ssl/2.8.4 OpenSSL/0.9.6b PHP/4.3.2 FrontPage/4.0.4.3
Connection: close
WWW-Authenticate: Digest
realm="protectedarea@wzlbrmpft.com",
domain="<domain>",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c0",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
qop="auth,auth-int",
stale="false",
algorithm="MD5"
Das ist zwar prinzipiell der gleiche Vorgang wie bei der Base64-Authentifizierung, aber der Eintrag
zu WWW-Authenticate ist doch wesentlioch komplexer. Im folgenden wird der Headereintrag erläutert.
algorithm (optional) | Der zu verwendete Algorithmus. Wenn die Angabe fehlt wird MD5 angenommen. |
domain (optional) | A comma separated list of URIs, as specified for HTTP/1.0. The intent is that the client could use this information to know the set of URIs for which the same authentication information should be sent. The URIs in this list may exist on different servers. If this keyword is omitted or empty, the client should assume that the domain consists of all URIs on the responding server. |
nonce | Ein String, der serverseitig erzeugt wird und ein Folge von Zufallscharactern enthält. Es wird empfohlen, daß er Hexziffern enthält. Dieser String wird dann clientseitig zusammen mit dem Passwort benützt, um daraus den Digest zu bestimmen. nonce kommt von n-umber once, weil ein Server einen nonce-Wert nur ein enziges Mal verschicken sollte (siehe auch stale). |
opaque (optional) | Ein String, der serverseitig erzeugt wird und ein Folge von Zufallscharactern enthält. Es wird empfohlen, daß er Hexziffern enthält. Falls dieser String übermittelt wird, so muß er vom Client unverändert an den Server zurückgeschickt werden. |
qop (optional) | This directive is optional, but is made so only for backward
compatibility with RFC 2069 ; it SHOULD be used by all
implementations compliant with this version of the Digest scheme.
If present, it is a quoted string of one or more tokens indicating
the "quality of protection" values supported by the server. The
value "auth" indicates authentication; the value "auth-int"
indicates authentication with integrity protection. (taken from RFC 2617).
Für den InternetExplorer ist der qop-Eintrag allerdings notwendig. Fehlt sie, funktioniert die Digest-Authentifizierung nicht korrekt. |
realm | Der Name eines geschützten Bereichs. Aus diesem Namen sollte der Client erschließen können, welchen Usernamen und welches Paßwort einzugeben ist. |
stale (optional) | Ein boolesches Attribut. Der Wert true bedeutet, daß der Wert von nonce veraltet ist und der client nochmal ein request machen muß um eine neue nonc zu bekommen. Im Normalfall ist stale false. Wenn die Angabe fehlt wird false angenommen. |
GET /protected/blabla.html HTTP/1.1 Authorization: Digest username="Mustafa", realm="geschuetzter Bereich", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c0", uri="/dir/index.html", qop=auth, nc=00000001, cnonce="0a4f113b", response="6629fae49393a05397450978507c4ef1", opaque="5ccc069c403ebaf9f0171e9517f40e41", algorithm="MD5"
Dabei bedeutet
username | The user's name in the specified realm. |
qop | Indicates what "quality of protection" the client has applied to the message. If present, its value MUST be one of the alternatives the server indicated it supports in the WWW-Authenticate header. These values affect the computation of the request-digest. Note that this is a single token, not a quoted list of alternatives as in WWW- Authenticate. This directive is optional in order to preserve backward compatibility with a minimal implementation of RFC 2069 [6], but SHOULD be used if the server indicated that qop is supported by providing a qop directive in the WWW-Authenticate header field. |
cnonce | This MUST be specified if a qop directive is sent (see above), and MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field. The cnonce-value is an opaque quoted string value provided by the client and used by both client and server to avoid chosen plaintext attacks, to provide mutual authentication, and to provide some message integrity protection. See the descriptions below of the calculation of the response- digest and request-digest values. |
nc | (nonce-count) This MUST be specified if a qop directive is sent (see above), and MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field. The nc-value is the hexadecimal count of the number of requests (including the current request) that the client has sent with the nonce value in this request. For example, in the first request sent in response to a given nonce value, the client sends "nc=00000001". The purpose of this directive is to allow the server to detect request replays by maintaining its own copy of this count - if the same nc-value is seen twice, then the request is a replay. |
response | A string of 32 hex digits computed as defined below, which proves
that the user knows a password |
uri | The URI from Request-URI of the Request-Line; duplicated here because proxies are allowed to change the Request-Line in transit. |
opaque | The values of the opaque and algorithm fields must be those supplied in the WWW-Authenticate response header for the entity being requested. |
algorithm | The values of the opaque and algorithm fields must be those supplied in the WWW-Authenticate response header for the entity being requested. |
Aufforderung zur Digest-Authentifizierung in Netscape bzw. Mozilla
Aufforderung zur Digest-Authentifizierung in InternetExplorer
Berechnung des clientseitigen Digest (response) durch den Browser
Der Browser berechnet nun drei verschiedene Digests, wobei die beiden ersten dazu dienen, den dritten
zu berechnen.
Auswertung der Clienteingaben
Aus dem eingebenen Benutzernamen, dem Paßwort und dem vom Server übermittelten Bereichsnamen (realm)
wird ein String nach folgendem Schema gebildet
Der String wird nun in eine Folge von (ASCII-code) Bytes verwandelt, hierzu wird der Digest berechnet
und das Ergebnis dann in einen String von Hexziffern verwandelt : digestString1.
Berechnung des zweiten Digest
Aus der Übertragungsmethode (in der Regel GET) und der vom Client angewählten URL wird nun ein zweiter
Digest berechnet. Dabei werden von der URL Protokoll und IP-Adresse weggelassen (dieser Rest heißt URI).
Auch dieser String wird nun in eine Folge von (ASCII-code) Bytes verwandelt, hierzu wird wieder der Digest
berechnet und auch dieses Ergebnis dann in einen String von Hexziffern verwandelt : digestString2.
Berechnung des dritten (und letzten) Digest
Die Berechnung des endgültigen Eintrages hängt davon ab, ob der Server den Parameter qop angegeben
hat.
qop wurde nicht angegeben oder hat nicht den Wert "auth"
qop wurde angegeben und hat den Wert "auth"
Dieser String wird nun ebenfalls wieder in eine Folge von (ASCII-code) Bytes verwandelt, hierzu wird
der Digest berechnet und dieses Ergebnis dann in einen String von Hexziffern verwandelt : digestString3.
Der letzte String bildet den response-Eintrag, den der Browser im Header unter Authorization an den
Server als Antwort auf die Authentifizierungsauffordung zurückschickt.
Ältere Browser sind nicht in der Lage, einen Digest zu berechnen.
Berechnung des Digest durch den Server
Da der Server den erhaltenen Digest nicht entschlüsseln kann (Stichwort one-way-function) berechnet
er nun seinerseits einen Digest nach exakt dem gleichen Schema wie der Client (Browser). Bis auf das
Paßwort besitzt er ja alle Rohdaten. Mit Hilfe des im Klartext übertragenen Usernamen holt er sich
das Paßwort aus einer (serverseitigen) Datenbank und kann damit mit den oben erwähnten drei Schritten
den Digest berechnen. Stellt sich derselbe Digest ein, so ist die Authentifizierung erfolgreich.
Eine HilfsKlasse für die Digest-Authentifizierung
Um die Digest-Authentifizierung abwickeln zu können muß ein Server eine nonce erzeugen können.
Generierung einer nonce
Obwohl es grundsätzlich egal ist, welchen String man als nonce verschickt, hat sich ein gewisser
Standard entwickelt. Für gewöhnlich ist eine nonce selbst wieder ein Digest der nach folgendem Schema
gebildet wird.
Dabei ist key ein auf dem Server hinterlegter String beliebigen, aber konstanten Inhalts.
Zu rawNonce wird nun nach dem bereits bekannten Schema ein Digest gebildet, der dann wieder in einen
Hexstring verwandelt wird. Durch currentTime wird sichergestellt, daß sich keine zwei gleichen
nonce-Werte ergeben können.
Die folgenden Methoden erzeugen mit Hilfe einer Clientadresse eine nonce.
public static String generateNOnce(String clientAddress)
{
long currentTime = System.currentTimeMillis();
String nOnceValue = clientAddress + ":" + currentTime + ":" + key;
byte buffer[] = md5.digest(nOnceValue.getBytes());
return = digest2HexString(buffer);
}
oder spezialisiert auf Servlets
public static String generateNOnce(HttpServletRequest req)
{
long currentTime = System.currentTimeMillis();
String nOnceValue = req.getRemoteAddr() + ":" + currentTime + ":" + key;
System.out.println("remoteaddress = " + req.getRemoteAddr() );
byte buffer[] = md5Digest.digest(nOnceValue.getBytes());
return = digest2HexString(buffer);
}
Die Dokumentation der Klasse folgt auf der nächsten Seite.