.NET Core验证ASP.NET密码
.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創建項目,然后用這個密碼注冊一個帳號:?
然后在數據庫中將哈希值取出來:
查得結果如下,結果為?AJRvdJ1/Ii+58zU6yBrPJH4hCkMagxqK/W6oejAuG1hIrNPEQMAAyYynsXWwat9Huw==:?
我將上述代碼稍作簡化,用最簡單的代碼表達驗證密碼的過程,除去兩行注釋代碼(用于做斷言),整個過程只需7行代碼,代碼如下:
此時,運行結果如下,運行顯示?True,表示驗證解碼成功:?
總結
我總結一個表格如下所示:
| 版本 | 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密码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【在路上2】快递的运单轨迹
- 下一篇: DevExpress作为企业赞助商加入.