MD5-Hash bei Java und C# geben nicht das selbe Ergebnis?
Hallo zusammen,
ich versuche in den letzten Tage mich mit der Entwickleroberfläche der FritzBox auseinander zu setzen. Leider scheitete ich am Session ID Login.
Der Login funktioniert über eine Challenge (String) den mir die FritzBox generiert und dem Passwort der FritzBox. Wenn ich den Login richtig verstanden habe geht er wie folgt:
Schritt 1: Challenge erhalten via fritz.box/login_sid.lua
Schritt 2: Alle Zeichen im Passwort der FritzBox, welche nicht in ISO-8859-1
anzeigbar sind (einen CodePoint von über 255 besitzen) mit einem "."
ersetzen
Schritt 3: Einen MD5-Hash im Format von 32 Hexzeichen mit Kleinbuchstaben
generieren aus dem String %Challenge%-%Passwort% (%Challenge% ist die
aus dem Schritt 1, %Passwort% ist das Passwort der FritzBox aus dem
Schritt 2)
Schritt 4: Das Ergebnis zum Anmelden nutzen:
http://fritz.box/login_sid.lua?response=%Challenge%-%Ergebnis%
(%Ergebnis% ist der
MD5-Hash aus Schritt 3)
So: Was ist nun mein Problem?
Ich möchte diesen MD5-Hash generieren. Ich habe mir bereits Tutorials angeschaut, wie die von baeldung.com und auch einen String als Ergebnis bekommen. Das Problem ist aber, dass der Hash falsch ist.
Also habe ich mir den Beispiel C#-Code auf der FritzBox Webseite angeschaut und ausprobiert. Das Ergebnis ist ein ganz anderer Hash. Wie kommt das zu Stande?
Damit man mich besser versteht, habe ich hier meinen Java Code und den Code aus C#.
Input bei allen:
51cbb71a-Test
Java (Möglichkeit 1): https://pastebin.com/vpzA8mr9 (um Platz zu sparen als Link)
Output:
DigestTest: 3e2a82112b721922d21a2e62e12f02212b02142c12692a82
Java (Möglichkeit 2):
StringBuilder builder = new StringBuilder();
for (byte b : DigestUtils.md5(input)) {
builder.append(byteToHex(b));
}
System.out.println("BuilderDigest: " + builder.toString());
Output:
BuilderDigest: 3ea811b7192d1ae6e1f021b014c169a8
Java (Möglichkeit 3):
String md5Hex = DigestUtils.md5Hex(input); //API: commons-codec-1.15.jar
System.out.println("Hash: " + md5Hex);
Output:
Hash: 3ea811b7192d1ae6e1f021b014c169a8
C#
static void Main(string[] args) {
try {
MD5 md5Hasher = MD5.Create();
byte[] encoded = Encoding.Unicode.GetBytes("51cbb71a-Test");
byte[] data = md5Hasher.ComputeHash(encoded);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < data.Length; i++) {
builder.Append(data[i].ToString("x2"));
}
Console.WriteLine(builder.ToString());
} catch(IndexOutOfRangeException exception) {
Console.WriteLine("null");
}
}
Output:
0e9af10abdb7cd7119a85967272e3bc3
Wieso kommt da was anderes raus und wie kann ich das nun auch in Java umsetzen? (Der C#-Code gibt die richtige Lösung!)
Danke schon einmal im Voraus!
Links die Helfen können:
3 Antworten
Ich zitiere aus der von dir verlinkten Doku:
Der MD5-Hash wird über die Bytefolge der UTF-16LE-Codierung dieses Strings gebildet (ohne BOM und ohne abschließende 0-Bytes).
Das ist ein bisschen unüblich und macht natürlich einen wesentlichen Unterschied. Dein Java-Code arbeitet nicht so, der in C# schon.
Der wesentliche Unterschied ist .getBytes(), da müsste sowas rein wie:
byte[] bytes = digest.digest(input.getBytes(StandardCharsets.UTF_16LE));
Danke dir! Leider nicht das selbe wie beim C#-Code aber ich werde trotzen schauen ob es funktioniert hat. :D
Wenn du's mit den DigestUtils machst:
DigestUtils.md5Hex(input.getBytes(StandardCharsets.UTF_16LE))
Damit bekomme ich auch 0e9af10abdb7cd7119a85967272e3bc3, wie im C#-Beispiel.
Leider mag die FritzBox die Response nicht. Es kann aber auch sein, dass ich irgendetwas grundlegend falsch verstehe. Ich schau mir mal die FritzBox Dokumentation genau an. Aber danke für deine Hilfe
Der erste Hash ist voller 2er, die da nicht rein gehören. Woher genau die kommen, musst du selber herausfinden.
Generell würde ich bei solchen Sachen auf Probleme mit ByteOrder oder Zahlenrepräsentation(signed/unsigned) tippen.
(Gerade bei deinem C# beispiel würde ich ersteres prüfen. Zudem, ob die Bytcodierungd es Strings dieselbe ist, wie die in Java. Bei verwendung eines anderen Encodings kann diese variieren.)
Woher die ganzen Zweien kommen weiß ich leider nicht. Ich habe leider von Byte Order oder Zahlenrepräsentation gar keine Ahnung. Wie Java und C# die MD5 Hashes verwaltet, bzw wie die FritzBox den Hash möchte verstehe ich leider fast gar nicht. Du kannst dieses ja auch meinen Schritten herausfinden. Wenn du eine Idee hättest, was ich für einen Java Code schreiben sollte, wäre ich dir / Ihnen sehr dankbar.
In deinem ersten javabeispiel wäre es sicherlich hilfreich, das Encoding beim umwandeln des Inputs in Bytes anzugeben.
Die zweien entstehen mit hoehr wahrscheinlichkeit erst beim ausgeben. Ich vermute, dass das dmait zusammenhängt, dass du eine Weite im Formatstring mit angegeben hast.
Beispielsweise.
Wobei die '0' eigentlich auch nicht nötig sein dürfte, wenn ich das richtig sehe:
https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/util/Formatter.html#syntax
In jedem Fall muss aber die Weite vor dem Conversion-String stehen.
Doch, die 0 ist nötig, sonst gibt's unerwünschte Leerzeichen (etwa " a" statt "0a"). Die Syntax ist praktisch identisch mit der von Formatstrings in printf() in C.
Ja, stimmt. Hatte das nur nicht in der Flagliste auf der verlinkten Siete gesehen. Die '0' wird dort wie ein kleines 'o' dargestellt.
In der detailierten Erklärung weiter unten ist die Darstellung allerdings korrekt.
Wenn dein C#-Code für 51cbb71a-Test den MD5-Hash 0e9af10abdb7cd7119a85967272e3bc3 raushaut, dann hast du im C#-Teil irgendeinen Fehler.
Korekt wäre 3ea811b7192d1ae6e1f021b014c169a8.
Interessanterweise ist trotzdem kein Fehler in deinem C#-Snippet zu erkennen, was die Fehlersuche nicht gerade vereinfacht...
Zum Testen kannst du auch einen online-Hashgenerator verwenden, z.B.:
https://www.md5hashgenerator.com/
Er zeigt, dass deine beiden Java-Rechnungen das richtige Ergebnis liefern, der C# Code hingegen nicht.
Den Fehler würde ich am ehesten bei der Berechnung der Hash-Eingabe vermuten:
Encoding.Unicode.GetBytes("51cbb71a-Test")
Das liefert wohl aus irgendeinem Grund nicht (exakt) die richtige Bytefolge. Man muss ja bedenken, dass jede geringfügige Änderung der Eingabe einen völlig anderen Hashwert ergibt.
Komischerweise soll dieser Code aber funktionieren. Hingegen meiner nicht (Java)
Ich habe den vagen Verdacht, dass der Unterschied in der Umwandlung von String zu Bytes liegt. Etwa hier:
Encoding.Unicode.GetBytes(...)
Aye, UTF-16 könnte da tatsächlich Probleme machen.
Encoding.UTF8.GetBytes(...)
sollte funktionieren.
Anscheinend liefert aber der falsche Code das von der Fritzbox gewünschte Ergebnis, wenn ich FS richtig verstehe.
Das Java-Resultat ist jedenfalls im üblichen Sinn richtig, man kann das ja auch mit den vielen Online-MD5-Generatoren überprüfen.
Aha, der FS hat die verlinkte Doku nicht gelesen:
Der MD5-Hash wird über die Bytefolge der UTF-16LE-Codierung dieses Strings gebildet (ohne BOM und ohne abschließende 0-Bytes).
Ich habe das gelesen und auch mal mit der UTF-16-LE Codierung ausprobiert. Der Code dafür ergab zuerst Chinesische Symbole und dann ein Ergebnis das Falsch war.
Leider bin ich nicht der Fachmann wenn es um Codierung und Byte To String, bzw String to Hash geht. Ich wäre sehr dankbar, wenn du mir eine Beispiellösung geben könntest oder etwas, was mir hilft, eine Lösung in Java zu finden.