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

基于PBKDF2的C#用户哈希密码验证不匹配问题排查

密码哈希验证不匹配问题排查

用户注册时将输入密码传入CreateHash()方法,得到包含Hash和Salt的Dictionary<string, string>,并将二者以varchar(256)类型存储到SQL数据库。用户登录时,从数据库取出Salt,与输入密码一同传入GetHash()方法,但返回的Hash与数据库中存储的不匹配,问题出在以下几点:

原代码

public class EncryptionHelper
{
    public const int SALT_SIZE = 128;
    public const int HASH_SIZE = 128;
    public const int ITERATIONS = 100000;

    // 生成密码哈希和盐
    public static Dictionary<string, string> CreateHash(string input)
    {
        Dictionary<string, string> hashAndSalt = new Dictionary<string, string>();
        RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
        byte[] salt = new byte[SALT_SIZE];
        provider.GetBytes(salt);

        Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(input, salt, ITERATIONS);
        hashAndSalt.Add("Hash", Encoding.Default.GetString(pbkdf2.GetBytes(HASH_SIZE)));
        hashAndSalt.Add("Salt", Encoding.Default.GetString(salt));
        return hashAndSalt;
    }

    // 验证密码时生成哈希
    public static string GetHash(string saltString, string passwordString)
    {
        byte[] salt = Encoding.ASCII.GetBytes(saltString);

        Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(passwordString, salt, ITERATIONS);
        return Encoding.Default.GetString(pbkdf2.GetBytes(HASH_SIZE));
    }
}

问题原因

  1. 编码规则不匹配:

    • 创建哈希时用Encoding.Default将二进制的salt和hash转成字符串,Encoding.Default是系统依赖编码(比如Windows默认GBK、Linux默认UTF-8);验证时却用Encoding.ASCII把salt字符串转回字节数组,两种编码的字符映射规则完全不同,会导致还原后的salt字节数组和原始salt不一致,最终生成的哈希自然不匹配。
  2. 二进制转字符串的方式错误:

    • salt和hash是纯二进制数据,并非合法的文本编码字节,直接用Encoding类转换会导致数据丢失或损坏。比如部分字节值在ASCII编码中无法表示,会被替换成默认占位符,反向转换时根本还原不出原始字节。

修正方案

改用Base64编码转换二进制与字符串,Base64专门设计用于安全地将二进制数据转成可存储的字符串,不会丢失任何信息。修正后的代码如下:

public class EncryptionHelper
{
    public const int SALT_SIZE = 128;
    public const int HASH_SIZE = 128;
    public const int ITERATIONS = 100000;

    public static Dictionary<string, string> CreateHash(string input)
    {
        Dictionary<string, string> hashAndSalt = new Dictionary<string, string>();
        using (var provider = new RNGCryptoServiceProvider())
        {
            byte[] salt = new byte[SALT_SIZE];
            provider.GetBytes(salt);

            using (var pbkdf2 = new Rfc2898DeriveBytes(input, salt, ITERATIONS))
            {
                hashAndSalt.Add("Hash", Convert.ToBase64String(pbkdf2.GetBytes(HASH_SIZE)));
                hashAndSalt.Add("Salt", Convert.ToBase64String(salt));
            }
        }
        return hashAndSalt;
    }

    public static string GetHash(string saltString, string passwordString)
    {
        byte[] salt = Convert.FromBase64String(saltString);
        using (var pbkdf2 = new Rfc2898DeriveBytes(passwordString, salt, ITERATIONS))
        {
            return Convert.ToBase64String(pbkdf2.GetBytes(HASH_SIZE));
        }
    }
}

额外补充:建议用using语句自动释放加密类资源,避免内存泄漏;数据库varchar(256)的长度足够存储Base64编码后的salt和hash(128字节二进制转Base64后约172字符)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 17:20:45