Advanced   Java   Services
Digest Authentication
back next up home

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

This document describes the MD5 message-digest algorithm. The algorithm takes as input a message of arbitrary length and produces as output a 128-bit "fingerprint" or "message digest" of the input. It is conjectured that it is computationally infeasible to produce two messages having the same message digest, or to produce any message having a given prespecified target message digest. The MD5 algorithm is intended for digital signature applications, where a large file must be "compressed" in a secure manner before being encrypted with a private (secret) key under a public-key cryptosystem such as RSA.


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 getInstance()-Methoden erhalten wir Objekte, die einen Digest nach einem bestimmten Algorithmus berechnen können.

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:

0111 1100    0001 0000    1000 1100    1111 0010    1100 1101    0000 0001    0111 1101    0101 0101
0111 1110    0011 1101    1010 0100    1001 1001    1110 1001    0000 0011    0000 0110    1110 0010

Die Hexzifferndarstellung dazu sieht folgendermaßen aus:

7c   10   8c   f2   cd   01   7d   55   7e   3d   a4   99   e9   03   06   e2

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.


Der RequestHeader mit Authentifizierung (kann etwa so aussehen)

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.

top back next up home