如何手动解密ASP.NET核心身份验证cookie?

时间:2017-03-16 18:49:54

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

让我们考虑一个众所周知的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将存储在客户端。你可以在任何浏览器devtools中看到cookie(在我的例子中它是分块的):

enter image description here

使用应用程序代码中的cookie不是问题(而不是问题)。

我的问题是:如何在应用程序之外解密cookie ?我想需要一个私钥,如何获得它?

我检查了docs,发现只有常用词:

  

这将创建一个加密的cookie并将其添加到当前   响应。必须在配置期间指定AuthenticationScheme   也可在调用SignInAsync时使用。

     

使用的加密是ASP.NET的数据保护   系统。如果您在多台计算机上托管,负载平衡或   使用Web场,您需要配置数据保护   使用相同的密钥环和应用程序标识符。

那么,是否有可能解密身份验证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。

4 个答案:

答案 0 :(得分:22)

在不需要密钥的情况下解密验证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,您需要匹配它们。因此,这行代码应该为您提供可用于解密身份验证cookie的IDataProtector

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();
}

此方法使用注释为构造函数的IDataProtectionProvider provider


将密钥持久保存到目录时解密验证Cookie

如果要在应用程序之间共享cookie,则可能决定将数据保护密钥保留在目录中。这可以通过将以下内容添加到startup.cs文件的ConfigureServices方法来完成:

services.AddDataProtection().PersistKeysToFileSystem(
        new DirectoryInfo(@"C:\temp-keys\")); 

要小心但因为密钥没有加密所以由你来保护它们!如果你绝对必须,只将密钥保存到目录中(或者如果你只是想了解系统的工作原理)。您需要指定使用这些密钥的cookie DataProtectionProvider。这可以通过startup.cs类的UseCookieAuthentication方法中的Configure配置来完成,如下所示:

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

答案 1 :(得分:6)

请参阅下面的.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所指出的那样,创建保护程序的狡猾方法正是Microsoft的操作方式(请参阅their code here)。因此,它可能会在将来的版本中更改。

答案 2 :(得分:1)

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);

答案 3 :(得分:0)

我刚刚在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解密端上,下面的代码将产生ClaimsIdentity在(例如).Net Core 3.0中设置的值:

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;
}