ASP.NET中将 PasswordHasher 使用的 PBKDF2 算法替换为更现代的 Scrypt 或 Argon2 算法

发布于:2025-04-13 ⋅ 阅读:(18) ⋅ 点赞:(0)

相关博文:

.Net实现SCrypt Hash加密_scrypt加密-CSDN博客

密钥派生算法介绍 及 PBKDF2(过时)<Bcrypt(开始淘汰)<Scrypt< Argon2(含Argon2d、Argon2i、Argon2id)简介-CSDN博客

浅述.Net中的Hash算法(顺带对称、非对称算法)_nthash 算法-CSDN博客

ASP.NET中将 PasswordHasher 使用的 PBKDF2 算法替换为更现代的 Scrypt 或 Argon2 算法

一、关于 Microsoft.AspNetCore.Identity.PasswordHasher.HashPassword 方法

 这是 ASP.NET Core Identity 框架中用于安全哈希用户密码的核心方法

技术原理

  1. 哈希算法

    • 默认使用 PBKDF2(基于密码的密钥派生函数)结合 HMAC-SHA256

    • 此算法通过多次迭代(如默认 100,000 次)显著增加计算成本,抵御暴力破解和彩虹表攻击。

  2. 哈希结果结构
    哈希后的字符串包含以下四部分(以竖线分隔):

    V2|10000|s4ltValV3...|h4shValV3...

    *个人注解:本人项目运行过程调试看了下,显示形式有所不同(如截图所示,貌似再加了base64编码,同时验证了多次计算结果并不相同):

    • 版本标识(如 V2):标识哈希算法的版本。

    • 迭代次数(如 10000):PBKDF2 的迭代次数。

    • 盐值(Salt):128 位随机生成的唯一盐值,确保相同密码哈希结果不同。

    • 子密钥(Subkey):256 位最终生成的哈希值。

        

  1. 自动盐值生成
    每次哈希都会生成唯一的盐值,彻底避免相同密码的哈希重复,增强安全性。

  2. PasswordHasher<TUser> 中的TUser 参数允许在哈希过程中加入用户属性(如将用户 ID 作为盐值的一部分),但默认实现未使用此特性。(个人注解:TUser 类型参数虽然在验证过程中也需要传递,但由于验证过程使用的是和计算的哈希一起存储的盐等参数信息,所以传递的TUser user实际并无作用,加密过程使用的盐值也是随机生成的,因此加密过程的TUser user实际也是无作用的,可能是预留给用户继承后自定义用的)

实际使用示例

using Microsoft.AspNetCore.Identity;

// 创建用户对象(示例)
var user = new IdentityUser { UserName = "test@example.com" };

// 初始化密码哈希器
var passwordHasher = new PasswordHasher<IdentityUser>();

// 哈希密码
string password = "MySecureP@ssw0rd";
string hashedPassword = passwordHasher.HashPassword(user, password);

// 验证密码
PasswordVerificationResult result = passwordHasher.VerifyHashedPassword(
    user, 
    hashedPassword, 
    "MySecureP@ssw0rd"
);

if (result == PasswordVerificationResult.Success) 
{
    Console.WriteLine("密码验证成功!");
}

二、替换为更现代的 Scrypt 或 Argon2 算法

在 ASP.NET Core Identity 中,默认的 PasswordHasher 使用 PBKDF2 算法,但若需要替换为更现代的 Scrypt 或 Argon2 算法(这两种算法被认为在抵御 GPU/ASIC 攻击时更安全),需要自定义实现。以下是完整的替换步骤:


1. 为什么选择 Scrypt 或 Argon2?

  • Argon2: 2015 年密码哈希竞赛冠军,支持内存硬性(Memory-hard)计算,抵御并行破解。

  • Scrypt: 设计时强调内存消耗大,增加硬件攻击成本。

  • 两者均被广泛认为比 PBKDF2 更适应现代硬件环境。


2. 实现自定义 PasswordHasher

步骤 1:安装依赖库

首先通过 NuGet 安装对应算法的库:

  • Argon2Conscious.Crypto.Argon2 或 Isopoh.Cryptography.Argon2

  • ScryptScrypt.NET 或 LibrePassword

 dotnet add package Isopoh.Cryptography.Argon2

步骤 2:继承并重写 PasswordHasher

创建一个自定义的 PasswordHasher 类,继承自 IPasswordHasher<TUser>

示例:Argon2 实现
using Microsoft.AspNetCore.Identity;
using Isopoh.Cryptography.Argon2;
using System.Text;

public class Argon2PasswordHasher<TUser> : IPasswordHasher<TUser>
    where TUser : class
{
    public string HashPassword(TUser user, string password)
    {
        // 配置 Argon2 参数
        var config = new Argon2Config
        {
            Type = Argon2Type.DataIndependentAddressing,
            Version = Argon2Version.Nineteen,
            TimeCost = 4,         // 迭代次数
            MemoryCost = 8192,    // 内存消耗 (KB)
            Lanes = 4,            // 并行度
            Password = Encoding.UTF8.GetBytes(password),
            Salt = Argon2.GenerateSalt(16) // 16 字节随机盐值
        };

        // 生成哈希
        using var argon2 = new Argon2(config);
        byte[] hashBytes = argon2.Hash();
        
        // 格式: 算法标记|参数|盐|哈希
        return $"Argon2|{config.TimeCost}|{config.MemoryCost}|{Convert.ToBase64String(config.Salt)}|{Convert.ToBase64String(hashBytes)}";
    }

    public PasswordVerificationResult VerifyHashedPassword(
        TUser user, 
        string hashedPassword, 
        string providedPassword)
    {
        // 解析存储的哈希值
        var parts = hashedPassword.Split('|');
        if (parts.Length != 5 || parts[0] != "Argon2")
        {
            // 如果不是 Argon2 格式,可能回退到默认验证(兼容旧哈希)
            return PasswordVerificationResult.Failed;
        }

        // 提取参数
        int timeCost = int.Parse(parts[1]);
        int memoryCost = int.Parse(parts[2]);
        byte[] salt = Convert.FromBase64String(parts[3]);
        byte[] expectedHash = Convert.FromBase64String(parts[4]);

        // 重新计算哈希
        var config = new Argon2Config
        {
            Type = Argon2Type.DataIndependentAddressing,
            Version = Argon2Version.Nineteen,
            TimeCost = timeCost,
            MemoryCost = memoryCost,
            Password = Encoding.UTF8.GetBytes(providedPassword),
            Salt = salt
        };

        using var argon2 = new Argon2(config);
        byte[] actualHash = argon2.Hash();

        // 对比哈希值
        return ConstantTimeEquals(expectedHash, actualHash) 
            ? PasswordVerificationResult.Success 
            : PasswordVerificationResult.Failed;
    }

    // 安全地对比字节数组(防止时序攻击)
    private static bool ConstantTimeEquals(byte[] a, byte[] b)
    {
        if (a.Length != b.Length) return false;
        uint diff = 0;
        for (int i = 0; i < a.Length; i++)
            diff |= (uint)(a[i] ^ b[i]);
        return diff == 0;
    }
}
 Scrypt 实现(类似逻辑)
using Scrypt;

public class ScryptPasswordHasher<TUser> : IPasswordHasher<TUser>
    where TUser : class
{
    private readonly ScryptEncoder _encoder = new ScryptEncoder();

    public string HashPassword(TUser user, string password)
    {
        return _encoder.Encode(password);
    }

    public PasswordVerificationResult VerifyHashedPassword(
        TUser user, 
        string hashedPassword, 
        string providedPassword)
    {
        return _encoder.Compare(providedPassword, hashedPassword) 
            ? PasswordVerificationResult.Success 
            : PasswordVerificationResult.Failed;
    }
}

3. 注册自定义 PasswordHasher

在 Startup.cs 或 Program.cs 中替换默认服务:

builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
    // 替换默认的 PasswordHasher
    options.Password.RequireDigit = false; // 可选:调整密码策略
    options.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<YourDbContext>()
.AddPasswordHasher<Argon2PasswordHasher<IdentityUser>>(); // 或 ScryptPasswordHasher

* 采用本人项目中是在Autofac IOC模块注入加载过程中注入的方式则应该为: 

public class RegisterModule : Autofac.Module
{

    ...      
    protected override void Load(ContainerBuilder builder)
    {
            ...       
            //密码哈希泛型注入            
            //builder.RegisterGeneric(typeof(PasswordHasher<>)).As(typeof(IPasswordHasher<>)).SingleInstance().PropertiesAutowired();
            builder.RegisterGeneric(typeof(Argon2PasswordHasher<>)).As(typeof(IPasswordHasher<>)).SingleInstance().PropertiesAutowired();
            ...        
    }
}

//再在接口控制器中注入:
private readonly Lazy<IPasswordHasher<UserEntity>> _passwordHasher;
private UserEntity user = new UserEntity();
构造方法(Lazy<IPasswordHasher<UserEntity>> passwordHasher)
{
    _passwordHasher = passwordHasher;
}
加密方法()
{
    var hashedPassword = _passwordHasher.Value.HashPassword(user, input.Password);
}
验证方法()
{
    var passwordVerificationResult = _passwordHasher.Value.VerifyHashedPassword(user,     user.Password, input.Password);
    valid = passwordVerificationResult == PasswordVerificationResult.Success || passwordVerificationResult == PasswordVerificationResult.SuccessRehashNeeded;
}

4. 参数调优建议

  • Argon2:

    • TimeCost: 迭代次数(通常 3-5)

    • MemoryCost: 内存大小(单位 KB,建议 ≥ 64MB 即 65536 KB)

    • Parallelism: 并行线程数(通常 2-4)

  • Scrypt:

    • IterationCount: 迭代次数(默认 16384)

    • BlockSize: 内存块大小(默认 8)

    • ThreadCount: 并行度(默认 1)

使用工具(如 OWASP 建议)测试参数,确保哈希耗时在 0.5-1 秒左右。


5. 数据库迁移与兼容性

  • 旧密码处理:
    在 VerifyHashedPassword 方法中,可先尝试新算法验证,若失败则回退到旧算法验证,并在验证成功后用新算法重新哈希存储。

  • 字段长度:
    Argon2/Scrypt 的哈希结果可能比 PBKDF2 更长,需确保数据库字段足够(建议 VARCHAR(255))。


6. 安全注意事项

  1. 不要自行实现算法,始终使用成熟的库(如 Isopoh.Cryptography.Argon2)。

  2. 参数需动态调整,未来硬件升级后需提高 MemoryCost 或 TimeCost

  3. 测试性能,确保哈希耗时在可接受范围内(通常 0.5-1 秒)。


通过以上步骤,即可将 ASP.NET Core Identity 的密码哈希算法替换为更安全的 Scrypt 或 Argon2。


网站公告

今日签到

点亮在社区的每一天
去签到