You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#实现PBKDF2-SHA256:参数不同却获相同哈希结果的问题

问题

我按照AVM的Session ID文档实现密码哈希流程:

  1. 通过HTTP请求获取Challenge,拆分得到:iter1=60000、salt1=f56cbe583d35a155cfbdf24c8d6bfa45,iter2=6000、salt2=99a2a02c9bd6989e52307a6e83bab646
  2. 先用PBKDF2-SHA256对密码和salt1进行iter1次哈希得到hash1
  3. 再用hash1和salt2进行iter2次哈希得到hash2

但运行C#代码后,hash1和hash2结果完全相同,输出如下:

Salt 1: f56cbe583d35a155cfbdf24c8d6bfa45 ~> 60000
Salt 2: 99a2a02c9bd6989e52307a6e83bab646 ~> 6000
Hashed PW: 53797374656D2E53656375726974792E43727970746F6772617068792E526663323839384465726976654279746573
Hashed Request: 53797374656D2E53656375726974792E43727970746F6772617068792E526663323839384465726976654279746573
Result: 99a2a02c9bd6989e52307a6e83bab646$53797374656d2e53656375726974792e43727970746f6772617068792e526663323839384465726976654279746573

我的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();
}

我已经尝试修改HMAC方法及Rfc2898DeriveBytes的使用方式,但问题仍存在。

解决方案

核心问题分析

代码存在两个致命错误:

  1. 错误调用Rfc2898DeriveBytes.ToString():该方法返回的是对象的类型名称(System.Security.Cryptography.Rfc2898DeriveBytes),并非PBKDF2哈希计算结果。因此无论参数如何变化,返回的字节数组始终相同。
  2. 盐值处理逻辑错误:从Challenge拆分得到的salt1、salt2是十六进制字符串,直接用Encoding.UTF8.GetBytes()转换会破坏原始盐值的字节结构,必须将十六进制字符串解码为字节数组。

修正后的代码

1. 修复PBKDF2哈希获取逻辑

将pbkdf2.ToString()替换为pbkdf2.GetBytes(32)(SHA256哈希长度为32字节):

private byte[] HMAC(byte[] input, byte[] salt, int iterations) {
    using (var pbkdf2 = new Rfc2898DeriveBytes(input, salt, iterations, HashAlgorithmName.SHA256)) {
        // SHA256对应哈希长度为32字节
        return pbkdf2.GetBytes(32);
    }
}

2. 添加十六进制字符串转字节数组方法

替换原有的盐值转换逻辑:

private byte[] HexStringToByteArray(string hex) {
    if (hex.Length % 2 != 0)
        throw new ArgumentException("十六进制字符串长度必须为偶数");
    
    byte[] bytes = new byte[hex.Length / 2];
    for (int i = 0; i < hex.Length; i += 2) {
        bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
    }
    return bytes;
}

修改CreateChallenge中的盐值转换代码:

byte[] salt1 = HexStringToByteArray(parts[2]);
byte[] salt2 = HexStringToByteArray(parts[4]);

3. 同步迭代次数注释(可选)

修正注释与实际值不符的问题:

int iter1 = int.Parse(parts[1]); // 60000
int iter2 = int.Parse(parts[3]); // 6000

验证效果

修正后,两次PBKDF2计算会基于正确的盐值和哈希结果生成不同的hash1和hash2,最终输出符合AVM文档要求。


内容的提问来源于stack exchange,提问作者Adrian Preuss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 07:58:15