相关博文:
.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 框架中用于安全哈希用户密码的核心方法
技术原理
-
哈希算法
-
默认使用 PBKDF2(基于密码的密钥派生函数)结合 HMAC-SHA256。
-
此算法通过多次迭代(如默认 100,000 次)显著增加计算成本,抵御暴力破解和彩虹表攻击。
-
-
哈希结果结构
哈希后的字符串包含以下四部分(以竖线分隔):V2|10000|s4ltValV3...|h4shValV3...
*个人注解:本人项目运行过程调试看了下,显示形式有所不同(如截图所示,貌似再加了base64编码,同时验证了多次计算结果并不相同):
-
版本标识(如
V2
):标识哈希算法的版本。 -
迭代次数(如
10000
):PBKDF2 的迭代次数。 -
盐值(Salt):128 位随机生成的唯一盐值,确保相同密码哈希结果不同。
-
子密钥(Subkey):256 位最终生成的哈希值。
-
-
自动盐值生成
每次哈希都会生成唯一的盐值,彻底避免相同密码的哈希重复,增强安全性。 -
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 安装对应算法的库:
-
Argon2:
Conscious.Crypto.Argon2
或Isopoh.Cryptography.Argon2
-
Scrypt:
Scrypt.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 =>
{// 替换默认的 PasswordHasheroptions.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. 安全注意事项
-
不要自行实现算法,始终使用成熟的库(如
Isopoh.Cryptography.Argon2
)。 -
参数需动态调整,未来硬件升级后需提高
MemoryCost
或TimeCost
。 -
测试性能,确保哈希耗时在可接受范围内(通常 0.5-1 秒)。
通过以上步骤,即可将 ASP.NET Core Identity 的密码哈希算法替换为更安全的 Scrypt 或 Argon2。