【问题标题】:How to manually decrypt an ASP.NET Core Authentication cookie?如何手动解密 ASP.NET Core 身份验证 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 给出了接受的答案,而不是我。他的代码是正确的。

标签: security authentication cookies asp.net-core asp.net-core-mvc


【解决方案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);

【讨论】:

【解决方案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;
}

【讨论】:

    猜你喜欢
    • 2017-03-30
    • 2018-09-02
    • 1970-01-01
    • 2018-02-24
    • 2021-06-19
    • 2017-02-15
    • 1970-01-01
    • 2017-12-31
    • 2018-03-30
    相关资源
    最近更新 更多