.NET Core验证ASP.NET密码

随着 .NETCore的持续更新和完善,越来越多的机构已经选择或者升级为 .NETCore。但由于技术不完全相同,不可能所有应用/数据库都能无缝迁移,因此 ASP.NETCore和传统 ASP.NET之间多少会存在一些挑战,需要更多的渐进升级方法和交互。

其中,密码共享就是升级到 ASP.NETCore一个很容易想到的渐进升级方式,也是一个需要解决的问题。

啥?什么都不用做?

其实如果坚持走 ASP.NETCoreIdentity这一套,代码是写了一个 if/else,能兼容老 ASP.NETIdentity生成的密码的。 Github链接:https://github.com/dotnet/aspnetcore/blob/8b7f6621695d93b2a55fb8a5b1be99c4af867ae4/src/Identity/Extensions.Core/src/PasswordHasher.cs#L185-L207,核心代码如下:

/// <summary>
/// Returns a <see cref="PasswordVerificationResult"/> indicating the result of a password hash comparison.
/// </summary>
/// <param name="user">The user whose password should be verified.</param>
/// <param name="hashedPassword">The hash value for a user's stored password.</param>
/// <param name="providedPassword">The password supplied for comparison.</param>
/// <returns>A <see cref="PasswordVerificationResult"/> indicating the result of a password hash comparison.</returns>
/// <remarks>Implementations of this method should be time consistent.</remarks>
public virtual PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword)
{if (hashedPassword == null){throw new ArgumentNullException(nameof(hashedPassword));}if (providedPassword == null){throw new ArgumentNullException(nameof(providedPassword));}byte[] decodedHashedPassword = Convert.FromBase64String(hashedPassword);// read the format marker from the hashed passwordif (decodedHashedPassword.Length == 0){return PasswordVerificationResult.Failed;}switch (decodedHashedPassword[0]){case 0x00:if (VerifyHashedPasswordV2(decodedHashedPassword, providedPassword)){// This is an old password hash format - the caller needs to rehash if we're not running in an older compat mode.return (_compatibilityMode == PasswordHasherCompatibilityMode.IdentityV3)? PasswordVerificationResult.SuccessRehashNeeded: PasswordVerificationResult.Success;}else{return PasswordVerificationResult.Failed;}case 0x01:int embeddedIterCount;if (VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, out embeddedIterCount)){// If this hasher was configured with a higher iteration count, change the entry now.return (embeddedIterCount < _iterCount)? PasswordVerificationResult.SuccessRehashNeeded: PasswordVerificationResult.Success;}else{return PasswordVerificationResult.Failed;}default:return PasswordVerificationResult.Failed; // unknown format marker}
}

它根据 Base64解码后的第一个字节,来判断是老版本还是新版本,然后调用各自不同的验证函数。

但时代在变化,很多人已经不用官方提供的这一套 Identity验证密码,那么有什么“骚”操作,可以解密吗?

和密码刚正面

传统的 ASP.NET MVC模板项目是通过 ASP.NETIdentity管理的密码,它由 Rfc2898DeriveBytes实现,该算法进行了 1000次循环 SHA1哈希、并加盐,以确保难以通过传统的哈希碰撞来确保密码的安全性,其核心代码如下( Github链接:https://github.com/aspnet/AspNetIdentity/blob/9c48993a446288032f9824633e6dae81257da06e/src/Microsoft.AspNet.Identity.Core/Crypto.cs#L26-L46):

private const int PBKDF2IterCount = 1000; // default for Rfc2898DeriveBytes
private const int PBKDF2SubkeyLength = 256/8; // 256 bits
private const int SaltSize = 128/8; // 128 bits
/* =======================
*  HASHED PASSWORD FORMATS
*  =======================
*
*  Version 0:
*  PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
*  (See also: SDL crypto guidelines v5.1, Part III)
*  Format: { 0x00, salt, subkey }
*/
public static string HashPassword(string password)
{if (password == null){throw new ArgumentNullException("password");}// Produce a version 0 (see comment above) text hash.byte[] salt;byte[] subkey;using (var deriveBytes = new Rfc2898DeriveBytes(password, SaltSize, PBKDF2IterCount)){salt = deriveBytes.Salt;subkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);}var outputBytes = new byte[1 + SaltSize + PBKDF2SubkeyLength];Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, PBKDF2SubkeyLength);return Convert.ToBase64String(outputBytes);
}

值得一提的是,代码用到了 Rfc2898DeriveBytes,经常读我的博客的知道,这个类是老朋友了,通过传入明文密码、盐、迭代次数和算法,可以在不依赖于哈希算法安全性的前提下做单向加密,确保了密码的不可(难以)破解性。

可见,密码的原始字节由 {0,salt,subkey}三部分组成,其中盐为 128位,即 16字节, subkey为 256位,即 32字节,总共 1+16+32=49字节,密文需要转换为 Base64,根据 Base64的信息量计算公式

Math.Log(64, 256)=0.75

可知, Base64编码相对原始字节的比例为 0.75:1,因此计算可得转换为 Base64之后,其字符串长度为

Math.Ceiling(49 / 0.75 / 4) * 4 = 68

我们来从数据库随便查询一个由 ASP.NETIdentity创建的密码:

SELECT TOP 1 PasswordHash, LEN(PasswordHash) AS Len
FROM [User]

结果如下,可见结果长度真的为 68

所以,说了这么多,怎么把密码迁移到 ASP.NETCore呢?

注意看,上面的代码没什么特别,就是依赖于 Rfc2898DeriveBytes这个类。

.NETCore内置了 Rfc2898DeriveBytes这个类,可以直接使用,不需要安装任何 NuGet包,因此……直接复制粘贴上文中的【核心代码】即可。

ASP.NETCore中的 Identity有什么区别?

Github核心代码在此:https://github.com/dotnet/aspnetcore/blob/8b7f6621695d93b2a55fb8a5b1be99c4af867ae4/src/Identity/Extensions.Core/src/PasswordHasher.cs#L113-L156

我就从简引用一下关键注释:

/* =======================
*  HASHED PASSWORD FORMATS
*  =======================
*
*  Version 2:
*  PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
*  (See also: SDL crypto guidelines v5.1, Part III)
*  Format: { 0x00, salt, subkey }
*
*  Version 3:
*  PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
*  Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
*  (All UInt32s are stored big-endian.)
*/

原来,老 ASP.NETIdentity中的密码版本为 V2,当前 ASP.NETCoreIdentity中的密码版本为 V3,首字节(版本号)从 0x00改成了 0x01,算法从 HMACSHA1升级为了 HMACSHA256,另外 V3版本还将迭代次数从 1000升级为 10000,另外还将算法名、迭代次数、盐的长度信息保存在了密码中。

另外需要注意的是,新 ASP.NETCoreIdentity的实现很有弹性,它通过依赖注入 IOptions<T>的形式,使得所有选项都是可以配置的。

解密示例

假如我有一个密码: myF&TB9vhTx7,我使用传统的 ASP.NET MVC创建项目,然后用这个密码注册一个帐号:

然后在数据库中将哈希值取出来:

SELECT PasswordHash
FROM [AspNetUsers]
WHERE [Email] = N'sdflysha@qq.com'

查得结果如下,结果为 AJRvdJ1/Ii+58zU6yBrPJH4hCkMagxqK/W6oejAuG1hIrNPEQMAAyYynsXWwat9Huw==

我将上述代码作简化,用最简单的代码表达验证密码的过程,除去两行注释代码(用于做断言),整个过程只需7行代码,代码如下:

string pwdHashText = "AJRvdJ1/Ii+58zU6yBrPJH4hCkMagxqK/W6oejAuG1hIrNPEQMAAyYynsXWwat9Huw==";
string pwd = "myF&TB9vhTx7";
byte[] pwdHash = Convert.FromBase64String(pwdHashText);
// Debug.Assert(pwdHash.Length == 49);
// Debug.Assert(pwdHash[0] == 0);
byte[] salt = pwdHash[1..17];
byte[] hash = pwdHash[17..49];
using var r = new Rfc2898DeriveBytes(pwd, salt, 1000);
Console.WriteLine(r.GetBytes(32).SequenceEqual(hash));

此时,运行结果如下,运行显示 True,表示验证解码成功:

总结

我总结一个表格如下所示:

ASP.NETIdentity ASP.NETCoreIdentity
版本 V2 V3
首字节 0x00 0x01
默认算法 HMACSHA1 HMACSHA256
迭代次数 1000 10000
密码信息 版本+盐+哈希值 版本+算法+迭代次数+盐长度+盐+哈希值
原始长度 1+16+32=49 1+4+4+4+16+32=61
Base64长度 68 84
可配置性 不可配置 可自由配置
安全性 非常好

密码解密其实是前往 ASP.NETCore上较为轻松的一步,核心代码也就 7行。

其实更有挑战、也更有意思的是如何解传统 ASP.NETIdentity中的 Cookie,我将在下一篇中详情分析这个主题,敬请期待!

喜欢的朋友请关注我的微信公众号:【DotNet骚操作】

新年快到了,祝大家阖家欢乐,鼠年大吉!

.NET Core验证ASP.NET密码相关推荐

  1. ASP.NET Core 用户注册 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 用户注册 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 用户注册 上一章节我们终于迁移完了 Identity 的数据,也创建 ...

  2. 用JS验证asp.net服务端控件

    可以用javascript来验证asp.net服务端的控件,不过目前只局限于验证textbox(输入框)不能为空的情况.还有一个前提条件是,提交按钮必须是html的控件,再加上一个runat=serv ...

  3. 用户姓名保护python_Python操作LDAP,对用户进行认证(验证用户名以及密码)

    Python操作LDAP,对用户进行认证 1.环境 Python版本: 3.7.1 操作系统:windows 7 x64 第三方包:python-ldap 2.LDAP LDAP,它是基于X.500标 ...

  4. java和asp.net core_.NET Core和ASP.NET Core简介与区别

    1..NET Core简介 .NET Core是适用于 windows.linux 和 macos 操作系统的免费.开源托管的计算机软件框架,是微软开发的第一个官方版本,具有跨平台 (Windows. ...

  5. 微软BUG Bounty悬赏项目扩展至.NET Core和ASP.NET Core

    微软宣布自 2016 年 9 月 1 日开始将 .NET Core 和 ASP.NET 纳入到 BUG Bounty 悬赏项目范围内,微软将会为 Windows 和 Linux 平台上的两个编程代码提 ...

  6. .NET Core amp; ASP.NET Core 1.0在Redhat峰会上正式发布

    众所周知,Red Hat和微软正在努力使.NET Core成为Red Hat企业版Linux (RHEL)系统上的一流开发平台选项.这个团队已经一起工作好几个月了,RHEL对.NET有许多需求.今天在 ...

  7. 最新版dotnet-cli下的ASP.NET Core和asp.net mvc【RC2尝鲜】

    RC1是http://get.asp.net里面提供的https://docs.asp.net/en/latest/getting-started/index.html,使用旧的ASP.NET Cor ...

  8. 确认密码参数php,laravel unique验证、确认密码confirmed验证以及密码修改验证的方法...

    confirmed 验证字段必须有一个匹配字段 foo_confirmation,例如,如果验证字段是 password,必须输入一个与之匹配的 password_confirmation 字段. s ...

  9. ASP.NET Core与ASP.NET区别

    随着.NET的发展,.NET Core对跨平台支持得越来越好,ASP.NET Core能运行在Windows.macOS 或 Linux 上,这就意味着ASP.NET Core开发的网站,可以在除了W ...

最新文章

  1. matlab教程lqg函数与收敛速度,科学网—Matlab编写由加速度积分得到速度和位移函数 - 王德才的博文...
  2. (剑指Offer)面试题18:树的子结构
  3. Java面试宝典系列之基础面试题String、变量、类与对象、集合类、SSH(一)
  4. Pythton3实例
  5. Windows Forms、MFC、WTL、WxWidgets、Qt、GTK综合比较
  6. Microsoft SQL server 2005的基本操作
  7. golang基础之三-字符串,时间,流程控制,函数
  8. [Bootstrap-Table] 中的事件用例
  9. 开博啦——半路出家做运维以来的一些杂感
  10. namenode 无法启动之每次开机需要重新格式化-tmp
  11. 用c语言编程图书管理系统,C语言图书管理系统简洁版
  12. C语言序列求和epa,Altera Innovateasia
  13. 思科模拟器路由器配置
  14. 如何快速提高idm下载速度?idm下载速度只有几十kb
  15. AR涂涂乐⭐三、 C#实现识别图进入扫描框显示绿色,未进入为红色功能
  16. 关于程序代码的时间复杂度
  17. 基于微信小程序的疫情智慧社区管理系统 计算机毕业设计 后台管理系统
  18. HIL测试系统的构建
  19. 我的世界服务器物品不堆叠,我的世界不可堆叠物品怎么堆叠
  20. MLDonkey命令手册

热门文章

  1. Leetcode: Valid Parentheses
  2. Windows API GetVersionEx()判断系统版本详解
  3. java bean 工厂模式_深入理解Java的三种工厂模式
  4. stm32cubeide外部中断_【STM32】HAL库 STM32CubeMX教程三----外部中断(HAL库GPIO讲解)
  5. powerpoint预览_如何安排PowerPoint幻灯片的时间以进行更有效的演示
  6. WebApi的调用-3.Basic验证
  7. Emoji:搜索将与您找到表情符号背后的故事
  8. android 开源组件合集-UI篇(2013-11-07更新)
  9. 【bayes】贝叶斯likelihood和model
  10. HDU 1964 Pipes