I've tried to calculate a hashed password.
The mechanism is decribed here:
First step, to get the challenge from an HTTP-Request with follwing result:
<?xml version="1.0" encoding="utf-8"?>
<SessionInfo>
<SID>0000000000000000</SID>
<Challenge>2$60000$f56cbe583d35a155cfbdf24c8d6bfa45$6000$99a2a02c9bd6989e52307a6e83bab646</Challenge>
<BlockTime>0</BlockTime>
<Rights></Rights>
<Users>
<User last="1">root</User>
</Users>
</SessionInfo>
The challenge is build as following data:
<type>$<ter1>$<salt1>$<ter2>$<salt2>
<iter1> = 60000
<salt1> = f56cbe583d35a155cfbdf24c8d6bfa45
<iter2> = 6000
<salt2> = 99a2a02c9bd6989e52307a6e83bab646
The password will hashed as following (source from code in PDF):
# Hash twice, once with static salt...
hash1 = hashlib.pbkdf2_hmac("sha256", password.encode(), salt1, iter1)
# Once with dynamic salt.
hash2 = hashlib.pbkdf2_hmac("sha256", hash1, salt2, iter2)
And here is my C# Code to try the hashing:
private void CreateChallenge(String challenge, LoginData data) {
// PBKDF2 (ab FRITZ!OS 7.24)
if(challenge.Contains("$")) {
String[] parts = challenge.Split('$');
String type = parts[0]; // 2
int iter1 = int.Parse(parts[1]); // 600000
byte[] salt1 = Encoding.UTF8.GetBytes(parts[2]); // HEX
int iter2 = int.Parse(parts[3]); // 600000
byte[] salt2 = Encoding.UTF8.GetBytes(parts[4]); // HEX
System.Diagnostics.Debug.Print("Salt 1: " + parts[2] + " ~> " + iter1);
System.Diagnostics.Debug.Print("Salt 2: " + parts[4] + " ~> " + iter2);
// Calculate
byte[] hash1 = HMAC(Encoding.UTF8.GetBytes(data.Password), salt1, iter1);
System.Diagnostics.Debug.Print("Hashed PW: " + BitConverter.ToString(hash1).Replace("-", ""));
byte[] hash2 = HMAC(hash1, salt2, iter2);
System.Diagnostics.Debug.Print("Hashed Request: " + BitConverter.ToString(hash2).Replace("-", ""));
this.CallSignin(parts[4] + "$" + BitConverter.ToString(hash2).Replace("-", "").ToLower(), data);
// MD5-Fallback
} else {
this.CallSignin(challenge + "-" + MD5(challenge + "-" + data.Password), data);
}
}
private byte[] HMAC(byte[] input, byte[] salt, int iterations) {
using (var pbkdf2 = new Rfc2898DeriveBytes(input, salt, iterations, HashAlgorithmName.SHA256)) {
return Encoding.UTF8.GetBytes(pbkdf2.ToString());
//return pbkdf2.GetBytes(length);
}
}
private String MD5(String input) {
MD5 hasher = System.Security.Cryptography.MD5.Create();
byte[] data = hasher.ComputeHash(Encoding.UTF8.GetBytes(input));
StringBuilder output = new StringBuilder();
for(int index = 0; index < data.Length; index++) {
output.Append(data[index].ToString("x2"));
}
return output.ToString().ToLower();
}
And here is a sample Call:
CreateChallenge("2$60000$f56cbe583d35a155cfbdf24c8d6bfa45$6000$99a2a02c9bd6989e52307a6e83bab646", new LoginData() {
Username = "root",
Password = "123Test"
});
But curiously i've get follwing output:
Salt 1: f56cbe583d35a155cfbdf24c8d6bfa45 ~> 60000
Salt 2: 99a2a02c9bd6989e52307a6e83bab646 ~> 6000
Hashed PW: 53797374656D2E53656375726974792E43727970746F6772617068792E526663323839384465726976654279746573
Hashed Request: 53797374656D2E53656375726974792E43727970746F6772617068792E526663323839384465726976654279746573
Result: 99a2a02c9bd6989e52307a6e83bab646$53797374656d2e53656375726974792e43727970746f6772617068792e526663323839384465726976654279746573
Question
WHY is the Hashed PW complete the same as Hashed Request?
Both using other values..
Can anyone help me?
Change the HMAC Method and tried to use other mechanics for Rfc2898DeriveBytes
Your
HMAC()method, which is supposed to perform a key derivation with PBKDF2, returnspbkdf2.ToString(), which always is System.Security.Cryptography.Rfc2898DeriveBytes regardless of the input data.Correct would be to return
pbkdf2.GetBytes(32)instead (this line is present in your code, but commented out for some reason).Note that PBKDF2 uses an HMAC internally, but is not identical to it, so the name of the method is misleading and it should be renamed.
Fix of the method
HMAC():A second problem is that the salt is hex encoded, so it must be hex decoded with
Convert.FromHexString()(and not UTF-8 encoded withEncoding.UTF8.GetBytes()).Fix in
CreateChallenge():The linked documentation describes a test case (here, sec. Calculating the Response Value):
When the fixed code for this test case is executed:
the expected results are returned:
Note: In .NET 5
Rfc2898DeriveBytes()requires a salt which is at least 8 bytes long, in higher versions of .NET this constraint does not exist. Since in the test case only a 3 bytes salt is appliedRfc2898DeriveBytes()cannot be used with .NET 5. Instead C#/BouncyCastle can be applied: