在 C# 中计算 PBKDF2 Sha256 - 相同的结果,不同的数据

问题描述 投票:0回答:1

我尝试计算哈希密码。

该机制描述如下:

德语: https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AVM_Technical_Note_-_Session_ID_deutsch_2021-05-03.pdf

英语: https://avm.de/fileadmin/user_upload/Global/Service/Schnittstellen/AVM_Technical_Note_-_Session_ID_english_2021-05-03.pdf

第一步,从 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

hash sha256 pbkdf2
1个回答
0
投票

为什么 Hashed PW 与 Hashed Request 完全一样?

您的

HMAC()
方法应该使用 PBKDF2 执行密钥派生,返回
pbkdf2.ToString()
,无论输入数据如何,它始终返回字符串 System.Security.Cryptography.Rfc2898DeriveBytes
正确的做法是返回
pbkdf2.GetBytes(32)
(此行存在于您的代码中,但由于某种原因被注释掉)。
请注意,PBKDF2 在内部使用 HMAC,但与它并不相同,因此该方法的名称具有误导性,应重命名。

修复方法

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();
}
© www.soinside.com 2019 - 2024. All rights reserved.