Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.7k views
in Technique[技术] by (71.8m points)

security - How to manually decrypt an ASP.NET Core Authentication cookie?

Let's consider a common-known ASP.NET Core scenario. Firstly we add the middleware:

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
    });
    //...
}

Then serialize a principal:

await HttpContext.Authentication.SignInAsync("MyCookie", principal);

After these two calls an encrypted cookie will be stored at the client side. You can see the cookie (in my case it was chunked) in any browser devtools:

chunked encrypted cookie generated by ASP.NET

It's not a problem (and not a question) to work with cookies from application code.

My question is: how to decrypt the cookie outside the application? I guess a private key is needed for that, how to get it?

I checked the docs and found only common words:

This will create an encrypted cookie and add it to the current response. The AuthenticationScheme specified during configuration must also be used when calling SignInAsync.

Under the covers the encryption used is ASP.NET's Data Protection system. If you are hosting on multiple machines, load balancing or using a web farm then you will need to configure data protection to use the same key ring and application identifier.

So, is it possible to decrypt the authentication cookie, and if so how?

UPDATE #1: Based on Ron C great answer and comments, I've ended up with code:

public class Startup
{
    //constructor is omitted...
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDataProtection().PersistKeysToFileSystem(
            new DirectoryInfo(@"C:emp-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:emp-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);
    }
}

Unfortunately this code always produces exception on Unprotect method call:

CryptographicException in Microsoft.AspNetCore.DataProtection.dll: Additional information: The payload was invalid.

I tested different variations of this code on several machines without positive result. Probably I made a mistake, but where?

UPDATE #2: My mistake was the DataProtectionProvider hasn't been set in UseCookieAuthentication. Thanks to @RonC again.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Decrypting the Authentication Cookie without needing the keys

It's worth noting that you don't need to gain access to the keys to decrypt the authentication cookie. You simply need to use the right IDataProtector created with the right purpose parameter, and subpurpose parameters.

Based on the CookieAuthenticationMiddleware source code https://github.com/aspnet/Security/blob/rel/1.1.1/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationMiddleware.cs#L4 it looks like the purpose you need to pass is typeof(CookieAuthenticationMiddleware). And since they are passing additional parameters to the IDataProtector you will need to match them. So this line of code should get you an IDataProtector that can be used to decrypt the authentication cookie:

var dataProtector = provider.CreateProtector(typeof(CookieAuthenticationMiddleware).FullName, Options.AuthenticationScheme, "v2");

Note thatOptions.AuthenticationScheme is just "MyCookie" in this case since that's what it was set to in the Configure method of the startup.cs file.

Here is an example action method for decrypting your authentication cookie two different ways:

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

This method uses an IDataProtectionProvider called provider that is constructor injected.


Decrypting the Authentication Cookie when persisting keys to a directory

If you want to share cookies between applications then you might decide to persist the data protection keys to a directory. This can be done by adding the following to the ConfigureServices method of the startup.cs file:

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

BE CAREFUL though because the keys are not encrypted so it's up to you to protect them!!! Only persist the keys to a directory if you absolutely must, (or if you are just trying to understand how the system works). You will also need to specify a cookie DataProtectionProvider that uses those keys. This can be done with the help of the UseCookieAuthentication configuration in the Configure method of the startup.cs class like so:

app.UseCookieAuthentication(new CookieAuthenticationOptions() {
        DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"C:emp-keys")),
        AuthenticationScheme = "MyCookie",
        CookieName = "MyCookie",
        LoginPath = new PathString("/Home/Login"),
        AccessDeniedPath = new PathString("/Home/AccessDenied"),
        AutomaticAuthenticate = true,
        AutomaticChallenge = true
    });

With that configuration done. You can now decrypt the authentication cookie with the following code:

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

You can learn more about this latter scenario here: https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/compatibility/cookie-sharing


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...