相关博文:
.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 =>
{
// 替换默认的 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. 安全注意事项
不要自行实现算法,始终使用成熟的库(如
Isopoh.Cryptography.Argon2
)。参数需动态调整,未来硬件升级后需提高
MemoryCost
或TimeCost
。测试性能,确保哈希耗时在可接受范围内(通常 0.5-1 秒)。
通过以上步骤,即可将 ASP.NET Core Identity 的密码哈希算法替换为更安全的 Scrypt 或 Argon2。