Как вручную расшифровать файл cookie для основной аутентификации ASP.NET?

Рассмотрим распространенный сценарий 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 (в моем случае он был помечен) в любом браузере devtools:

введите описание изображения здесь

Это не проблема (и не вопрос) для работы с кукисами из кода приложения.

Мой вопрос: как расшифровать файл cookie вне приложения? Я думаю, для этого нужен секретный ключ, как его получить?

Я проверил docs и нашел только общие слова:

Это создаст зашифрованный файл cookie и добавит его в текущий ответ. Параметр AuthenticationScheme, указанный во время настройки, должен также можно использовать при вызове SignInAsync.

В оболочках используется шифрование ASP.NET Data Protection система. Если вы размещаете на нескольких машинах, балансировку нагрузки или используя веб-ферму, вам необходимо настроить защиту данных на используйте одно и то же ключевое кольцо и идентификатор приложения.

Итак, можно ли расшифровать файл cookie аутентификации, и если да, то как?

ОБНОВЛЕНИЕ # 1: Основанный на Ron C отличный ответ и комментарии, я получил код:

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:

CryptographicException в Microsoft.AspNetCore.DataProtection.dll: Дополнительная информация: полезная нагрузка недействительна.

Я тестировал различные варианты этого кода на нескольких машинах без положительного результата. Вероятно, я допустил ошибку, но где?

ОБНОВЛЕНИЕ # 2: Моя ошибка заключалась в том, что DataProtectionProvider не был установлен в UseCookieAuthentication. Благодаря @RonC снова.

Ответ 1

Расшифровка куки файла аутентификации без ключей

Стоит отметить, что вам не нужно получать доступ к ключам для расшифровки куки файла аутентификации. Вам просто нужно использовать правильный IDataProtector созданный с правильным целевым параметром, и параметры подзадачи.

На основе исходного кода CookieAuthenticationMiddleware https://github.com/aspnet/Security/blob/rel/1.1.1/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationMiddleware.cs#L4 это выглядит как цель, которую вам нужно передать это typeof(CookieAuthenticationMiddleware). А поскольку они передают дополнительные параметры в IDataProtector вам нужно будет сопоставить их. Таким образом, эта строка кода должна предоставить вам IDataProtector который можно использовать для расшифровки куки файла аутентификации:

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

Обратите внимание, что в этом случае Options.AuthenticationScheme является просто "MyCookie", поскольку он был задан в методе Configure файла startup.cs.

Вот пример метода действия для дешифрования вашего куки аутентификации двумя различными способами:

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 между приложениями, вы можете сохранить ключи защиты данных в каталоге. Это можно сделать, добавив следующее в метод ConfigureServices файла startup.cs:

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

БУДЬТЕ ОСТОРОЖНЫ, потому что ключи не зашифрованы, так что вам решать защищать их !!! Сохраняйте ключи в каталоге только в случае крайней необходимости (или если вы просто пытаетесь понять, как работает система). Вам также необходимо указать файл cookie DataProtectionProvider который использует эти ключи. Это можно сделать с помощью конфигурации UseCookieAuthentication в методе Configure класса startup.cs следующим образом:

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

С этой конфигурацией сделано. Теперь вы можете расшифровать куки аутентификации с помощью следующего кода:

 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

Ответ 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, хитрый способ создания защитника - это именно то, как это делает Microsoft (см. Их код здесь). Поэтому это может измениться в будущих версиях.

Ответ 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);