.NET Core验证ASP.NET密码

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

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

啥?什么都不用做?

其实如果坚持走ASP.NET Core Identity这一套,代码是写了一个if/else,能兼容老ASP.NET Identity生成的密码的,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 password
if (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.NET Identity管理的密码,它由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字节,subkey256位,即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.NET Identity创建的密码:

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

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

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

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

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

ASP.NET Core中的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.NET Identity中的密码版本为V2,当前ASP.NET Core Identity中的密码版本为V3,首字节(版本号)从0x00改成了0x01,算法从HMACSHA1升级为了HMACSHA256,另外V3版本还将迭代次数从1000升级为10000,另外还将算法名、迭代次数、盐的长度信息保存在了密码中。

另外需要注意的是,新ASP.NET Core Identity的实现很有弹性,它通过依赖注入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.NET Identity ASP.NET Core Identity
版本 V2 V3
首字节 0x00 0x01
默认算法 HMACSHA1 HMACSHA256
迭代次数 1000 10000
密码信息 版本+盐+哈希值 版本+算法+迭代次数+盐长度+盐+哈希值
原始长度 1+16+32=49 1+4+4+4+16+32=61
Base64长度 68 84
可配置性 不可配置 可自由配置
安全性 非常好

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

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

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

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

.NET Core验证ASP.NET密码的更多相关文章

  1. asp.net core 使用identityServer4的密码模式来进行身份认证(一)

    IdentityServer4是ASP.NET Core的一个包含OpenID和OAuth 2.0协议的框架.具体Oauth 2.0和openId请百度. 前言本博文适用于前后端分离或者为移动产品来后 ...

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

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

  3. 比较ASP.NET和ASP.NET Core[经典 Asp.Net v和 Asp.Net Core (Asp.Net Core MVC)]

    ASP.NET Core是.与.Net Core FrameWork一起发布的ASP.NET 新版本,最初被称为ASP.NET vNext,有一系列的命名变化,ASP.NET 5.0,ASP.NET ...

  4. ASP.NET Core 中间件 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 中间件 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 中间件 上一章节中,我们我们有讲到 Startup 类中的 Confi ...

  5. MVC3 数据验证用法之密码验证设计思路

    描述:MVC数据验证使用小结 内容:display,Required,stringLength,Remote,compare,RegularExpression 本人最近在公司用mvc做了一个修改密码 ...

  6. Android 设置代理(验证用户名和密码)

    这几天在研究在Android中,解析网页,但是公司内容,链接外网需要代理,并需要验证用户名和密码,十分头疼,网上查了下,没有头绪,最后总算在一个外国博客中看到类似的,记录下 URL url = new ...

  7. js用正则表达式验证用户和密码的安全性,生成随机验证码

    制作了一个表单,表单验证用户.密码.随机验证码 html页面

  8. .NET Core 和 ASP.NET 5 RC1 发布

    昨天微软发布了 .NET Core 和 ASP.NET 5 候选版本,支持 Windows,Linux 和 OS X 平台,版本 License 为 "Go Live",,也就是说 ...

  9. easyui-validatebox 验证两次密码是否输入一致

    验证两次密码是否输入一致 $.extend($.fn.validatebox.defaults.rules, {        /*必须和某个字段相等*/        equalTo: { vali ...

随机推荐

  1. Java语言中使用OpenMP

    从去年年中,开始学习Java,主要是维护公司用Java编写的服务器软件.目前,该服务器软件遇到一个问题,在下载大文件时,如果同时下载的用户很多, 服务器软件工作会出现异常,有的用户无法下载.服务器硬件 ...

  2. react + webpack 多页面搭建

    一.利用 creat-react-app 新建一个react单页面应用. cnpm i -g create-react-app create-react-app demo cd demo npm st ...

  3. Java 学习笔记(10)——容器

    之前学习了java中从语法到常用类的部分.在编程中有这样一类需求,就是要保存批量的相同数据类型.针对这种需求一般都是使用容器来存储.之前说过Java中的数组,但是数组不能改变长度.Java中提供了另一 ...

  4. read、write 与recv、send区别 gethostname

    recv相对于read有什么区别呢? 其实它跟read函数功能一样,都可以从套接口缓冲区sockfd中取数据到buf,但是recv仅仅只能够用于套接口IO,并不能用于文件IO以及其它的IO,而read ...

  5. c# T4模板生成实体类(sqlserver)

    1.用vs新建tt文件. 2.tt文件保存就自动运行 3.tt文件代码如下,设置生成cs文件的命名空间和生成地址 <#@ template language="C#" hos ...

  6. 【题解】CTS2019珍珠(二项式反演+卷积)

    [题解]CTS2019珍珠 题目就是要满足这样一个条件\(c_i\)代表出现次数 \[ \sum {[\dfrac {c_i } 2]} \ge 2m \] 显然\(\sum c_i=n\)所以,而且 ...

  7. 洛谷$P2168\ [NOI2015]$荷马史诗 贪心

    正解:贪心 解题报告: 传送门$QwQ$ 昂这个就哈夫曼树板子题鸭$QwQ$,只是从二叉变成多叉了$QwQ$ 考虑用类似合并果子的方法?就从两个变成$k$个了嘛,一样的鸭,然后就做完了$QwQ$ 注意 ...

  8. JDK1.8的HashMap实现原理和源码解析

    哈希表(hash table)也叫散列表,是一种非常重要的数据结构.许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,本文会对java集合框架中的对应实现HashMap的 ...

  9. IntelliJ IDEA安装教程及使用方法

    IntelliJ IDEA安装教程及使用方法 首先,需要去下载IDEA,直接在百度搜索IntelliJ IDEA,然后找到官网点击进入 进入官网,选择[Download],点击进入,选择自己的操作系统 ...

  10. bootstrap 轮播craousel 采坑之(修改默认鼠标浮动轮播不停止)

    首先上bootstrap 官网 https://v3.bootcss.com/javascript/#carousel 设置这个参数就可以,后面说如何采坑.见代码 html 部分 <!-- 轮播 ...