如何手动解密 ASP.NET Core 身份验证 cookie?
Posted
技术标签:
【中文标题】如何手动解密 ASP.NET Core 身份验证 cookie?【英文标题】:How to manually decrypt an ASP.NET Core Authentication cookie? 【发布时间】:2017-08-08 03:00:14 【问题描述】:让我们考虑一个众所周知的 ASP.NET Core 场景。首先我们添加中间件:
public void Configure(IApplicationBuilder app)
app.UseCookieAuthentication(new CookieAuthenticationOptions()
AuthenticationScheme = "MyCookie",
CookieName = "MyCookie",
LoginPath = new PathString("/Home/Login/"),
AccessDeniedPath = new PathString("/Home/AccessDenied/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
);
//...
然后序列化一个主体:
await HttpContext.Authentication.SignInAsync("MyCookie", principal);
在这两个调用之后,一个加密的 cookie 将被存储在客户端。您可以在任何浏览器开发工具中看到 cookie(在我的情况下是分块的):
使用来自应用程序代码的 cookie 不是问题(也不是问题)。
我的问题是:如何解密应用外的cookie?我想这需要一个私钥,如何获得它?
我查了docs,发现只有常用词:
这将创建一个加密的 cookie 并将其添加到当前 回复。配置期间指定的 AuthenticationScheme 必须 也可以在调用 SignInAsync 时使用。
在幕后使用的加密是 ASP.NET 的数据保护 系统。如果您在多台机器上托管,负载平衡或 使用网络场,那么您需要将数据保护配置为 使用相同的密钥环和应用程序标识符。
那么,是否可以解密身份验证 cookie,如果可以,如何解密?
更新 #1: 基于 Ron C great answer and comments,我最终得到了代码:
public class Startup
//constructor is omitted...
public void ConfigureServices(IServiceCollection services)
services.AddDataProtection().PersistKeysToFileSystem(
new DirectoryInfo(@"C:\temp-keys\"));
services.AddMvc();
public void Configure(IApplicationBuilder app)
app.UseCookieAuthentication(new CookieAuthenticationOptions()
AuthenticationScheme = "MyCookie",
CookieName = "MyCookie",
LoginPath = new PathString("/Home/Index/"),
AccessDeniedPath = new PathString("/Home/AccessDenied/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
);
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
public class HomeController : Controller
public async Task<IActionResult> Index()
await HttpContext.Authentication.SignInAsync("MyCookie", new ClaimsPrincipal());
return View();
public IActionResult DecryptCookie()
var provider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));
string cookieValue = HttpContext.Request.Cookies["MyCookie"];
var dataProtector = provider.CreateProtector(
typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(false, true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
return Content(plainText);
不幸的是,这段代码总是在Unprotect
方法调用上产生异常:
Microsoft.AspNetCore.DataProtection.dll 中的 CryptographicException: 附加信息:有效负载无效。
我在几台机器上测试了这段代码的不同变体,但没有得到肯定的结果。可能我犯了一个错误,但是在哪里?
更新 #2: 我的错误是 DataProtectionProvider
尚未在 UseCookieAuthentication
中设置。再次感谢@RonC。
【问题讨论】:
你能用正确的代码更新你的答案吗? @RonC 给出了接受的答案,而不是我。他的代码是正确的。 【参考方案1】:无需密钥即可解密身份验证 Cookie
值得注意的是,您无需访问密钥即可解密身份验证 cookie。您只需使用使用正确用途参数和子用途参数创建的正确IDataProtector
。
基于CookieAuthenticationMiddleware
源代码https://github.com/aspnet/Security/blob/rel/1.1.1/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationMiddleware.cs#L4 看起来您需要传递的目的是typeof(CookieAuthenticationMiddleware)
。而且由于它们将附加参数传递给IDataProtector
,因此您需要匹配它们。所以这行代码应该会得到一个IDataProtector
,可以用来解密身份验证cookie:
var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, Options.AuthenticationScheme, "v2");
请注意,在这种情况下Options.AuthenticationScheme
只是“MyCookie”,因为这是在 startup.cs 文件的 Configure
方法中设置的。
下面是一个示例操作方法,用于以两种不同的方式解密您的身份验证 cookie:
public IActionResult DecryptCookie()
//Get the encrypted cookie value
string cookieValue = HttpContext.Request.Cookies["MyCookie"];
//Get a data protector to use with either approach
var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");
//Get the decrypted cookie as plain text
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
//Get the decrypted cookie as a Authentication Ticket
TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookieValue);
return View();
此方法使用一个名为provider
的IDataProtectionProvider
,它是构造函数注入的。
在将密钥持久保存到目录时解密身份验证 Cookie
如果您想在应用程序之间共享 cookie,那么您可能会决定将数据保护密钥保存到目录中。这可以通过将以下内容添加到 startup.cs 文件的ConfigureServices
方法来完成:
services.AddDataProtection().PersistKeysToFileSystem(
new DirectoryInfo(@"C:\temp-keys\"));
小心因为密钥没有加密,所以由你来保护它们!!!只有在绝对必须的情况下(或者如果您只是想了解系统的工作原理),才将密钥保存到目录中。您还需要指定一个使用这些密钥的 cookie DataProtectionProvider
。这可以借助 startup.cs 类的 Configure
方法中的 UseCookieAuthentication
配置来完成,如下所示:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\")),
AuthenticationScheme = "MyCookie",
CookieName = "MyCookie",
LoginPath = new PathString("/Home/Login"),
AccessDeniedPath = new PathString("/Home/AccessDenied"),
AutomaticAuthenticate = true,
AutomaticChallenge = true
);
完成该配置。您现在可以使用以下代码解密身份验证 cookie:
public IActionResult DecryptCookie()
ViewData["Message"] = "This is the decrypt page";
var user = HttpContext.User; //User will be set to the ClaimsPrincipal
//Get the encrypted cookie value
string cookieValue = HttpContext.Request.Cookies["MyCookie"];
var provider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));
//Get a data protector to use with either approach
var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, "MyCookie", "v2");
//Get the decrypted cookie as plain text
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
//Get teh decrypted cookies as a Authentication Ticket
TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookieValue);
return View();
您可以在此处了解有关后一种情况的更多信息:https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/compatibility/cookie-sharing
【讨论】:
关于一些我一直在质疑但不需要深入研究的非常好的帖子。 看起来在 Core 2.0 中 CookieAuthenticationMiddleware 类已被替换,但仍然使用“Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware”的字符串值。 github.com/aspnet/Security/blob/… 我仍然在使用 .NET core 2.0 和分块 cookie 时遇到问题。有任何想法吗?我将 cookie 值连接在一起并使用 DataProtectionProvider 中的 cookie 名称。谢谢。 如果您在添加数据保护服务services.AddDataProtection().PersistKeysToFileSystem(new System.IO.DirectoryInfo(@"C:\temp-keys\")).SetApplicationName("MyApplicationName");
时指定了应用程序名称,请务必在DataProtectionProvider.Create
调用中指定相同的名称:var dataProtection = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"), cfg => cfg.SetApplicationName("MyApplicationName"));
@RonC 感谢您的回复。我可能读错了,但这个页面可能表明这是可能的? docs.microsoft.com/en-us/aspnet/core/security/…【参考方案2】:
请参阅下面的 .NET Core 2 从 cookie 获取声明的帮助方法:
private IEnumerable<Claim> GetClaimFromCookie(HttpContext httpContext, string cookieName, string cookieSchema)
// Get the encrypted cookie value
var opt = httpContext.RequestServices.GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>();
var cookie = opt.CurrentValue.CookieManager.GetRequestCookie(httpContext, cookieName);
// Decrypt if found
if (!string.IsNullOrEmpty(cookie))
var dataProtector = opt.CurrentValue.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", cookieSchema, "v2");
var ticketDataFormat = new TicketDataFormat(dataProtector);
var ticket = ticketDataFormat.Unprotect(cookie);
return ticket.Principal.Claims;
return null;
正如@Cirem 所指出的,创建保护器的狡猾方式正是微软的做法(参见their code here)。因此,它可能会在未来的版本中发生变化。
【讨论】:
它对我来说效果很好,而且似乎比公认的答案更容易使用。非常感谢。 这显然是由于 cookie 架构。 cookie 架构到底是什么? @liang,这可能是由于使用了较新版本的 .NET Core。 MS 在 v2.2 中更改了我的链接中提到的代码,并在 v3 中完全重新设计了它。【参考方案3】:ASP.NET Core 2.2 的另一个变体:
var cookieManager = new ChunkingCookieManager();
var cookie = cookieManager.GetRequestCookie(HttpContext, ".AspNetCore.Identity.Application");
var dataProtector = dataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "Identity.Application", "v2");
//Get the decrypted cookie as plain text
UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookie);
byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
string plainText = specialUtf8Encoding.GetString(plainBytes);
//Get teh decrypted cookies as a Authentication Ticket
TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookie);
【讨论】:
dataProtectionProvider从何而来? @Pancake 可以在构造函数 IDataProtectionProvider dataProtectionProvider 中使用 DI 注入。您还需要将其添加到服务中:docs.microsoft.com/en-us/aspnet/core/security/data-protection/…【参考方案4】:在 ASP.NET Core 应用程序中,您可以使用 CookieAuthenticationOptions.TicketDataFormat.Unprotect(cookieValue)
。
这里,我写了一个简单的静态(!)方法:
public static AuthenticationTicket DecryptAuthCookie(HttpContext httpContext)
// ONE - grab the CookieAuthenticationOptions instance
var opt = httpContext.RequestServices
.GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>()
.Get(CookieAuthenticationDefaults.AuthenticationScheme); //or use .Get("Cookies")
// TWO - Get the encrypted cookie value
var cookie = opt.CookieManager.GetRequestCookie(httpContext, opt.Cookie.Name);
// THREE - decrypt it
return opt.TicketDataFormat.Unprotect(cookie);
在 .NET 5 和 .NET 6 下工作正常。
我添加这个答案以供参考,因为如果您搜索如何手动解密 ASP.NET auth cookie,每个搜索引擎都会弹出这个问题。
【讨论】:
.. 在 .NET 6 下工作正常 对于 ASP.NET Core Identity,必须使用 .Get("Identity.Application") 而不是 .Get(CookieAuthenticationDefaults.AuthenticationScheme);【参考方案5】:我刚刚在 Classic ASP.net (4.6.1) 中得到了这个工作。请注意以下必需的安装:
Microsoft.Owin.Security.Interop
(将附带一堆依赖项 - 请注意,由于异常,我使用了 3.0.1 版本,但这可能不是必需的)。
Microsfot.AspNetCore.DataProtection
(会附带一堆依赖)
4.6.1 框架的标准网络资料
以下常量由框架定义:
PROVIDER_NAME = "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware"
SCHEME_NAME = "Identity.Application"
COOKIE_NAME = ".AspNetCore.Identity.Application"
(可定制)
以下常量是特定于配置的,但在应用程序之间必须相同。
APP_NAME = "Auth.Test.App"
SHARED_KEY_DIR = "C:\\app-keyring"
过程:
This article 有助于在双方进行此设置,尤其是在正确配置 .Net Core 方面。因此,我们将把它作为练习留给读者。
完成这些设置后,在 4.6.1 解密端,以下代码将生成(例如).Net Core 3.0 中设置的 ClaimsIdentity
:
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Owin.Security.Interop;
using System.IO;
using System.Security.Claims;
using System.Web;
public static ClaimsIdentity GetClaimsIdentity(HttpContext context)
//Get the encrypted cookie value
var cookie = context.Request.Cookies[Constants.COOKIE_NAME];
if (cookie == null)
return null;
var cookieValue = cookie.Value;
//Get a data protector to use with either approach
var keysDir = new DirectoryInfo(Constants.SHARED_KEY_DIR);
if (!keysDir.Exists) keysDir.Create();
var provider = DataProtectionProvider.Create(keysDir,
options => options.SetApplicationName(Constants.APP_NAME));
var dataProtector = provider.CreateProtector(Constants.PROVIDER_NAME, Constants.SCHEME_NAME, "v2");
//Get the decrypted cookie as a Authentication Ticket
var shim = new DataProtectorShim(dataProtector);
var ticketDataFormat = new AspNetTicketDataFormat(shim);
var ticket = ticketDataFormat.Unprotect(cookieValue);
return ticket.Identity;
【讨论】:
以上是关于如何手动解密 ASP.NET Core 身份验证 cookie?的主要内容,如果未能解决你的问题,请参考以下文章
没有 ASP.NET 身份的 .NET Core 外部身份验证
如何在 ASP.NET Core 中将角色添加到 Windows 身份验证
如何在 ASP.NET Core 2.0 中设置多个身份验证方案?
如何使 ASP.NET Core 中的身份验证 cookie 无效?