我尝试计算哈希密码。
该机制描述如下:
第一步,从 HTTP 请求中获取质询,结果如下:
<?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>
挑战由以下数据构成:
<type>$<ter1>$<salt1>$<ter2>$<salt2>
<iter1> = 60000
<salt1> = f56cbe583d35a155cfbdf24c8d6bfa45
<iter2> = 6000
<salt2> = 99a2a02c9bd6989e52307a6e83bab646
密码将按如下方式进行哈希处理(来源自 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)
这是我的
C#
尝试散列的代码:
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();
}
这是一个示例呼叫:
CreateChallenge("2$60000$f56cbe583d35a155cfbdf24c8d6bfa45$6000$99a2a02c9bd6989e52307a6e83bab646", new LoginData() {
Username = "root",
Password = "123Test"
});
但是奇怪的是我得到了以下输出:
Salt 1: f56cbe583d35a155cfbdf24c8d6bfa45 ~> 60000
Salt 2: 99a2a02c9bd6989e52307a6e83bab646 ~> 6000
Hashed PW: 53797374656D2E53656375726974792E43727970746F6772617068792E526663323839384465726976654279746573
Hashed Request: 53797374656D2E53656375726974792E43727970746F6772617068792E526663323839384465726976654279746573
Result: 99a2a02c9bd6989e52307a6e83bab646$53797374656d2e53656375726974792e43727970746f6772617068792e526663323839384465726976654279746573
问题
为什么
Hashed PW
与 Hashed Request
完全一样?
都使用其他值..
有人可以帮助我吗?
改变
HMAC
方法并尝试使用其他机制Rfc2898DeriveBytes
为什么 Hashed PW 与 Hashed Request 完全一样?
您的
HMAC()
方法应该使用 PBKDF2 执行密钥派生,返回 pbkdf2.ToString()
,无论输入数据如何,它始终返回字符串 System.Security.Cryptography.Rfc2898DeriveBytes。pbkdf2.GetBytes(32)
(此行存在于您的代码中,但由于某种原因被注释掉)。修复方法
HMAC()
:
private static byte[] HMAC(byte[] input, byte[] salt, int iterations)
{
using (var pbkdf2 = new Rfc2898DeriveBytes(input, salt, iterations, HashAlgorithmName.SHA256))
{
return pbkdf2.GetBytes(32); // apply GetBytes()
}
}
第二个问题是盐是十六进制编码的,因此必须使用
Convert.FromHexString()
进行十六进制解码(而不是使用 Encoding.UTF8.GetBytes()
进行 UTF-8 编码)。
修复
CreateChallenge()
:
...
byte[] salt1 = Convert.FromHexString(parts[2]); // hex decode...
...
byte[] salt2 = Convert.FromHexString(parts[4]); // hex decode...
...
链接的文档描述了一个测试用例:
示例挑战“2$10000$5A1711$2000$5A1722”和密码 “1example!”(utf8 编码)结果为:
哈希1 = pbdkf2_hmac_sha256(“1个例子!”, 5A1711, 10000)
=> 0x23428e9dec39d95ac7a40514062df0f9e94f996e17c398c79898d0403b332d3b(十六进制)响应 = 5A1722$ + pbdkf2_hmac_sha256(hash1, 5A1722, 2000).hex()
=> 5A1722$1798a1672bca7c6463d6b245f82b53703b0f50813401b03e4045a5861e689adb
执行此测试用例的固定代码时:
CreateChallenge("2$10000$5A1711$2000$5A1722", new LoginData()
{
Username = "root",
Password = "1example!"
});
返回预期结果:
Hashed PW: 23428E9DEC39D95AC7A40514062DF0F9E94F996E17C398C79898D0403B332D3B
Hashed Request: 1798A1672BCA7C6463D6B245F82B53703B0F50813401B03E4045A5861E689ADB
注意:在 .NET 5 中
Rfc2898DeriveBytes()
需要至少 8 个字节长的盐,在更高版本的 .NET 中,此约束不存在。由于在测试用例中仅应用了 3 字节盐,Rfc2898DeriveBytes()
不能与 .NET 5 一起使用。相反,可以应用 C#/BouncyCastle:
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
...
private static byte[] HMAC(byte[] input, byte[] salt, int iterations)
{
var pbkdf2 = new Pkcs5S2ParametersGenerator(new Org.BouncyCastle.Crypto.Digests.Sha256Digest());
pbkdf2.Init(input, salt, iterations);
var pbkdf2KeyParam = (KeyParameter)pbkdf2.GenerateDerivedMacParameters(32 * 8);
return pbkdf2KeyParam.GetKey();
}