ASP.NETCore如何解密Cookie

前言

事情的起点就在于我在弄ASP .NET Core下的OAuth2.0流程,想要看看具体的Cookie信息有哪些,发现Cookie信息被加密了,检索了一番,发现了解决办法 如何解密Cookie ,然后顺便找了一下原理。

如何解密Cookie

主要代码来源:How to manually decrypt an ASP.NET Core Authentication cookie?

  1. Startup.CongigureServices里注册的部分代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    services.AddAuthentication(config =>
    {
    //当我们登陆后,设置Cookie
    config.DefaultSignInScheme = "MyCookieScheme";
    //使用作为验证是否登陆
    config.DefaultAuthenticateScheme = "MyCookieScheme";
    }).AddCookie("MyCookieScheme", options =>
    {
    //设置密钥存储位置,你也可以使用services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(@"C:\temp-keys2\"))
    options.DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));
    options.Cookie.Name = "MyClientCookie"; //默认为:.AspNetCore.Cookies
    });
  2. 解密Cookie

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    [HttpGet]
    public IActionResult DecryptCookie()
    {
    //获得加密了的Cookie值,Cookie名假如注册时候没指定的话为:.AspNetCore.Cookies
    string cookieValue = HttpContext.Request.Cookies["MyClientCookie"];

    //使用Create方法生成DataProtectionProvider对象,密钥存储位置值与在ConfigureServices注册时一致
    var provider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));

    //获得DataProtector对象,第二个参数的值你在注册Cookies时候设定的Scheme名,没有设定的话其默认值为:Cookies
    var dataProtector = provider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "MyCookieScheme", "v2");

    //解密Cookie值,变为普通文本
    byte[] protectedBytes = Base64UrlTextEncoder.Decode(cookieValue);
    byte[] plainBytes = dataProtector.Unprotect(protectedBytes);
    string plainText = Encoding.UTF8.GetString(plainBytes);
    //上面的Encoding.UTF8.GetString不行的话使用下面的
    //UTF8Encoding specialUtf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
    //string plainText = specialUtf8Encoding.GetString(plainBytes);

    //或解密Cookie值,转换为AuthenticationTicket对象
    TicketDataFormat ticketDataFormat = new TicketDataFormat(dataProtector);
    AuthenticationTicket ticket = ticketDataFormat.Unprotect(cookieValue);

    return Ok(JsonConvert.SerializeObject(ticket.Properties.Items));
    }

注意

  • 最重要的是设置保存到 C:\temp-keys\ 下的私钥文件,有了这文件,你加密的Cookie能被任何人解密。

  • 当然你可以不存储到文件,还有其他办法,具体参考:ASP.NET Core 中的密钥存储提供程序

简单说说原理

默认Cookie不加密

当我们直接添加的Cookie默认是不经过加密的。

1
HttpContext.Response.Cookies.Append("web_nmae","MyWeb");

认证(Authentication)使用Cookie认证方案时认证信息加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//注册
services.AddAuthentication(config =>
{
//但我们登陆后,设置Cookie
config.DefaultSignInScheme = "MyCookieScheme";
//使用作为验证是否登陆
config.DefaultAuthenticateScheme = "MyCookieScheme";
}).AddCookie("MyCookieScheme", options =>
{
//options.DataProtectionProvider = DataProtectionProvider.Create(new DirectoryInfo(@"C:\temp-keys\"));
options.Cookie.Name = "MyClientCookie";
});

//SignIn接口
[HttpGet]
public async Task<ActionResult> SignIn()
{
var claims = new Claim[] {
new Claim(ClaimTypes.Name,"singo"),
new Claim(ClaimTypes.NameIdentifier,"4433"),
new Claim(ClaimTypes.Role,"system"),
new Claim(ClaimTypes.Role,"admin")
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await HttpContext.SignInAsync(claimsPrincipal);
return Ok("SignIn成功");
}

[HttpGet]
[Authorize]
public ActionResult SetCookie(string key,string value)
{
//使用该方法设置Cookie不会被加密
HttpContext.Response.Cookies.Append(key, value);
return Ok("SetCookie成功");
}

我们调用SignIn接口,查看Cookies,就能发现一个Cookie项,其value被加密了。

1
2
key: MyClientCookie
value: CfDJ8FY8TudSY7lKgK5Kc1rjrPANTxtvOw87CVoObdVDdNug6_nTvgFZWRANLhPQBRrrhBQjWItfG51Xsdc1SaXDRTBDfxLWl-iSzKX5NnUUf3U9hM5ptcrXkPbDnvZKWtjtgr9gY1A5lIbqsZTjDwrQR5jNBxu81LzCz8_vJBRma2yrmjc9UIc_3vyxXX5pkMWk1Rt6-QHXFuRUdHbHjuN_jRckKFmzIUHJ_dlpzTGbhwTeMRLUJwAJT6zP56XeR_A_k00GEULumSHYqCopbhOKHe89kA75_tuj2Sg1EPVK0oJ1DfGIusleH3qdbWN4ZRsFQ0QLYdalm05i0r1nx_cbGJPQhT0thFqNj-zdOkXKaEEZvGrl6kSQr4NM0IhtBed2HeibJsw9gSKVCNiQAPTMVoGnVnM4cr6A1e6uyEn9CO4IB-VhjULeE8GT0BJ0nD6VatLKq6aXbxVS5Mwfnhs4ZgxpFJ_u-IY3hL3joOYfmAcIJi1R3XFypmPAAp3AeMHn9qIuxKpCDF8ybQSPqRn_wIfLA9KUt5WGlZX5y_nvdfiX-HaPEmytZtyi69k5MPZxmOAeTHh9r1i6HCRYN6veFmjDC7y7kJhSxvxiWePmTfHx

查看HttpContext.SignInAsync源码

然后去找源代码看,最后会发现其中的关键就是CookieAuthenticationHandler.cs文件下的HandleSignInAsync方法,我把其简化了贴出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
protected override async Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
{
// Process the request cookie to initialize members like _sessionKey.
await EnsureCookieTicket();
var cookieOptions = BuildCookieOptions();

var signInContext = new CookieSigningInContext(
Context,
Scheme,
Options,
user,
properties,
cookieOptions);

if (!signInContext.Properties.ExpiresUtc.HasValue)
{
signInContext.Properties.ExpiresUtc = issuedUtc.Add(Options.ExpireTimeSpan);
}

await Events.SigningIn(signInContext);

if (signInContext.Properties.IsPersistent)
{
var expiresUtc = signInContext.Properties.ExpiresUtc ?? issuedUtc.Add(Options.ExpireTimeSpan);
signInContext.CookieOptions.Expires = expiresUtc.ToUniversalTime();
}

var ticket = new AuthenticationTicket(signInContext.Principal!, signInContext.Properties, signInContext.Scheme.Name);

//在这里加密cookieValue
//当你点到TicketDataFormat,会有提示如下
//The TicketDataFormat is used to protect and unprotect the identity and other properties which are stored in the
//cookie value. If not provided one will be created using <see cref="DataProtectionProvider"/>.
var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());

Options.CookieManager.AppendResponseCookie(
Context,
Options.Cookie.Name!,
cookieValue,
signInContext.CookieOptions);

var signedInContext = new CookieSignedInContext(
Context,
Scheme,
signInContext.Principal!,
signInContext.Properties,
Options);

await Events.SignedIn(signedInContext);

// Only redirect on the login path
var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
await ApplyHeaders(shouldRedirect, signedInContext.Properties);

Logger.AuthenticationSchemeSignedIn(Scheme.Name);
}

可以看到最后的加密的依赖就是 Data Protection

Data Protection

Data Protection 是微软提供的数据保护机制:

为了确保 Web 应用敏感数据的安全存储,该机制提供了一个简单、基于非对称加密改进的、性能良好的、开箱即用的加密 API 用于数据保护。

它不需要开发人员专门学习怎么样管理这些钥(公钥,私钥),系统回自动的选择算法和管理密钥的生命周期。理想情况下开发人员都不应该访问这些钥的原始文件。

结论

当我们 SignIn 时会把认证信息给 Data Protection 加密然后存储到 Cookie。

当我们获得其该 Cookie 进行认证时,该值又会被 Data Protection 给解密,默认该流程不需要用户设置,应用就会自动进行加解密。

关于Data Protection可扩展阅读

ASP.NET Core 数据保护(Data Protection)【上】

ASP.NET Core 数据保护(Data Protection)【中】

官方文档:

ASP.NET Core 数据保护