如何手动解密 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();

此方法使用一个名为providerIDataProtectionProvider,它是构造函数注入的。

在将密钥持久保存到目录时解密身份验证 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 =&gt; 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 无效?

如何在 ASP.NET Core 3 上同时使用 Azure AD 身份验证和身份?

如何从 ASP.NET Core 2.0 中的自定义中间件请求身份验证