在.NET Core中使用MachineKey

在.NET Core中使用MachineKey

在上篇文章中,我介绍了 Cookie是基于 MachineKey生成的, MachineKey决定了 Cookie生成的算法和密钥,并如果使用多台服务器做负载均衡时,必须指定一致的 MachineKey

但在 .NETCore中,官方似乎并没有提供 MachineKey实现,这为兼容 .NETFramework的 Cookie造成了许多障碍。

今天我将深入探索 MachineKey这个类,看看里面到底藏了什么东西,本文的最后我将使用 .NETCore来解密一个 ASP.NET MVC生成的 Cookie

认识MachineKey

在 .NETFramework中, machineKey首先需要一个配置,写在 app.config或者 web.config中,格式一般如下:

<machineKey validationKey="128个hex字符" decryptionKey="64个hex字符" validation="SHA1" decryption="AES" />

网上能找到可以直接生成随机 MachineKey的网站:https://www.developerfusion.com/tools/generatemachinekey/

但 MachineKey的 validationKey和 decryptionKey的内容只要符合长度和 hex要求,都是可以随意指定的,所以 machineKey生成器的意义其实不大。

探索MachineKey

打开 MachineKey的源代码如下所示(有删减):

public static class MachineKey {public static byte[] Unprotect(byte[] protectedData, params string[] purposes) {// ...有删减return Unprotect(AspNetCryptoServiceProvider.Instance, protectedData, purposes);}// Internal method for unit testing.internal static byte[] Unprotect(ICryptoServiceProvider cryptoServiceProvider, byte[] protectedData, string[] purposes) {// If the user is calling this method, we want to use the ICryptoServiceProvider// regardless of whether or not it's the default provider.Purpose derivedPurpose = Purpose.User_MachineKey_Protect.AppendSpecificPurposes(purposes);ICryptoService cryptoService = cryptoServiceProvider.GetCryptoService(derivedPurpose);return cryptoService.Unprotect(protectedData);}
}

具体代码可参见:https://referencesource.microsoft.com/#system.web/Security/MachineKey.cs,209

可见它本质是使用了 AspNetCryptoServiceProvider.Instance,然后调用其 GetCryptoService方法,然后获取一个 cryptoService,最后调用 Unprotect,注意其中还使用了一个 Purpose的类,依赖非常多。

AspNetCryptoServiceProvider

其中 AspNetCryptoServiceProvider.Instance的定义如下(有删减和整合):

internal sealed class AspNetCryptoServiceProvider : ICryptoServiceProvider {private static readonly Lazy<AspNetCryptoServiceProvider> _singleton = new Lazy<AspNetCryptoServiceProvider>(GetSingletonCryptoServiceProvider);internal static AspNetCryptoServiceProvider Instance {get {return _singleton.Value;}}private static AspNetCryptoServiceProvider GetSingletonCryptoServiceProvider() {// Provides all of the necessary dependencies for an application-level// AspNetCryptoServiceProvider.MachineKeySection machineKeySection = MachineKeySection.GetApplicationConfig();return new AspNetCryptoServiceProvider(machineKeySection: machineKeySection,cryptoAlgorithmFactory: new MachineKeyCryptoAlgorithmFactory(machineKeySection),masterKeyProvider: new MachineKeyMasterKeyProvider(machineKeySection),dataProtectorFactory: new MachineKeyDataProtectorFactory(machineKeySection),keyDerivationFunction: SP800_108.DeriveKey);}
}

具体代码可见:https://referencesource.microsoft.com/#system.web/Security/Cryptography/AspNetCryptoServiceProvider.cs,68dbd1c184ea4e88

可见它本质是依赖于 AspNetCryptoServiceProvider,它使用了 MachineKeyCryptoAlgorithmFactory、 MachineKeyMasterKeyProvider、 MachineKeyDataProtectorFactory,以及一个看上去有点奇怪的 SP800_108.DeriveKey

AspNetCryptoServiceProvider的 GetCryptoService方法如下:

public ICryptoService GetCryptoService(Purpose purpose, CryptoServiceOptions options = CryptoServiceOptions.None) {ICryptoService cryptoService;if (_isDataProtectorEnabled && options == CryptoServiceOptions.None) {// We can only use DataProtector if it's configured and the caller didn't ask for any special behavior like cacheabilitycryptoService = GetDataProtectorCryptoService(purpose);}else {// Otherwise we fall back to using the <machineKey> algorithms for cryptographycryptoService = GetNetFXCryptoService(purpose, options);}// always homogenize errors returned from the crypto servicereturn new HomogenizingCryptoServiceWrapper(cryptoService);
}
private NetFXCryptoService GetNetFXCryptoService(Purpose purpose, CryptoServiceOptions options) {// Extract the encryption and validation keys from the provided Purpose objectCryptographicKey encryptionKey = purpose.GetDerivedEncryptionKey(_masterKeyProvider, _keyDerivationFunction);CryptographicKey validationKey = purpose.GetDerivedValidationKey(_masterKeyProvider, _keyDerivationFunction);// and return the ICryptoService// (predictable IV turned on if the caller requested cacheable output)return new NetFXCryptoService(_cryptoAlgorithmFactory, encryptionKey, validationKey, predictableIV: (options == CryptoServiceOptions.CacheableOutput));
}

注意其中有一个判断,我结合 dnSpy做了认真的调试,发现它默认走的是 GetNetFXCryptoService,也就是注释中所谓的 <machineKey>算法。

然后 GetNetFXCryptoService方法依赖于 _masterKeyProvider和 _keyDerivationFunction用来生成两个 CryptographicKey,这两个就是之前所说的 MachineKeyMasterKeyProvider和 MachineKeyDataProtectorFactory

注意其中还有一个 HomogenizingCryptoServiceWrapper类,故名思义,它的作用应该是统一管理加密解释过程中的报错,实际也确实如此,我不作深入,有兴趣的读者可以看看原始代码在这:https://referencesource.microsoft.com/#system.web/Security/Cryptography/HomogenizingCryptoServiceWrapper.cs,25

最后调用 NetFXCryptoService来执行 Unprotect任务。

NetFXCryptoService

这个是重点了,源代码如下(有删减):

internal sealed class NetFXCryptoService : ICryptoService {private readonly ICryptoAlgorithmFactory _cryptoAlgorithmFactory;private readonly CryptographicKey _encryptionKey;private readonly bool _predictableIV;private readonly CryptographicKey _validationKey;// ...有删减// [UNPROTECT]// INPUT: protectedData// OUTPUT: clearData// ALGORITHM://   1) Assume protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))//   2) Validate the signature over the payload and strip it from the end//   3) Strip off the IV from the beginning of the payload//   4) Decrypt what remains of the payload, and return it as clearDatapublic byte[] Unprotect(byte[] protectedData) {// ...有删减using (SymmetricAlgorithm decryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {// 省略约100行代码????}}
}

这个代码非常长,我直接一刀全部删减了,只保留注释。如果不理解先好好看注释,不理解它在干嘛,直接看代码可能非常难,有兴趣的可以直接先看看代码:https://referencesource.microsoft.com/#system.web/Security/Cryptography/NetFXCryptoService.cs,35

首先看注释:

protectedData := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))

加密之后的数据由 IV、 密文以及 签名三部分组成;

其中 密文使用 encryptionKey、 IV和 原始明文加密而来;

签名由 validationKey作验证,传入参数是 IV以及 密文(这一点有点像 jwt)。

现在再来看看代码:

int ivByteCount = decryptionAlgorithm.BlockSize / 8; // IV length is equal to the block size
int signatureByteCount = validationAlgorithm.HashSize / 8;

IV的长度由解密算法的 BlockSize决定,签名算法的长度由验证算法的 BlockSize决定,有了 IV和 签名的长度,就知道了密文的长度:

int encryptedPayloadByteCount = protectedData.Length - ivByteCount - signatureByteCount;

下文就应该是轻车熟路,依葫芦画瓢了,先验证签名:

byte[] computedSignature = validationAlgorithm.ComputeHash(protectedData, 0, ivByteCount + encryptedPayloadByteCount);
if (/*验证不成功*/) {return null;
}

然后直接解密:

using (MemoryStream memStream = new MemoryStream()) {using (ICryptoTransform decryptor = decryptionAlgorithm.CreateDecryptor()) {using (CryptoStream cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Write)) {cryptoStream.Write(protectedData, ivByteCount, encryptedPayloadByteCount);cryptoStream.FlushFinalBlock();// At this point// memStream := clearDatabyte[] clearData = memStream.ToArray();return clearData;}}
}

可见这个类都是一些“正常操作”。之后我们来补充一下遗漏的部分。

MachineKeyCryptoAlgorithmFactory

首先是 MachineKeyCryptoAlgorithmFactory,代码如下(只保留了重点):

switch (algorithmName) {case "AES":case "Auto": // currently "Auto" defaults to AESreturn CryptoAlgorithms.CreateAes;case "DES":return CryptoAlgorithms.CreateDES;case "3DES":return CryptoAlgorithms.CreateTripleDES;default:return null; // unknown
}
switch (algorithmName) {case "SHA1":return CryptoAlgorithms.CreateHMACSHA1;case "HMACSHA256":return CryptoAlgorithms.CreateHMACSHA256;case "HMACSHA384":return CryptoAlgorithms.CreateHMACSHA384;case "HMACSHA512":return CryptoAlgorithms.CreateHMACSHA512;default:return null; // unknown
}

源代码链接在这:https://referencesource.microsoft.com/#system.web/Security/Cryptography/MachineKeyCryptoAlgorithmFactory.cs,14

可见非常地直白、浅显易懂。

MachineKeyMasterKeyProvider

然后是 MachineKeyMasterKeyProvider,核心代码如下:

private CryptographicKey GenerateCryptographicKey(string configAttributeName, string configAttributeValue, int autogenKeyOffset, int autogenKeyCount, string errorResourceString) {byte[] keyMaterial = CryptoUtil.HexToBinary(configAttributeValue);// If <machineKey> contained a valid key, just use it verbatim.if (keyMaterial != null && keyMaterial.Length > 0) {return new CryptographicKey(keyMaterial);}// 有删减
}public CryptographicKey GetEncryptionKey() {if (_encryptionKey == null) {_encryptionKey = GenerateCryptographicKey(configAttributeName: "decryptionKey",configAttributeValue: _machineKeySection.DecryptionKey,autogenKeyOffset: AUTOGEN_ENCRYPTION_OFFSET,autogenKeyCount: AUTOGEN_ENCRYPTION_KEYLENGTH,errorResourceString: SR.Invalid_decryption_key);}return _encryptionKey;
}
public CryptographicKey GetValidationKey() {if (_validationKey == null) {_validationKey = GenerateCryptographicKey(configAttributeName: "validationKey",configAttributeValue: _machineKeySection.ValidationKey,autogenKeyOffset: AUTOGEN_VALIDATION_OFFSET,autogenKeyCount: AUTOGEN_VALIDATION_KEYLENGTH,errorResourceString: SR.Invalid_validation_key);}return _validationKey;
}

可见这个类就是从 app.configweb.config中读取两个 xml位置的值,并转换为 CryptographicKey,然后 CryptographicKey的本质就是一个字节数组 byte[]

注意,原版的 GenerateCrytographicKey函数其实很长,但重点确实就是前面这三行代码,后面的是一些骚操作,可以自动从一些配置的位置生成 machineKey,这应该和 machineKey节点缺失或者不写有关,不在本文考虑的范畴以内。有兴趣的读者可以参见原始代码:https://referencesource.microsoft.com/#system.web/Security/Cryptography/MachineKeyMasterKeyProvider.cs,87

MachineKeyDataProtectorFactory

其源代码如下(有删减):

internal sealed class MachineKeyDataProtectorFactory : IDataProtectorFactory {public DataProtector GetDataProtector(Purpose purpose) {if (_dataProtectorFactory == null) {_dataProtectorFactory = GetDataProtectorFactory();}return _dataProtectorFactory(purpose);}private Func<Purpose, DataProtector> GetDataProtectorFactory() {string applicationName = _machineKeySection.ApplicationName;string dataProtectorTypeName = _machineKeySection.DataProtectorType;Func<Purpose, DataProtector> factory = purpose => {// Since the custom implementation might depend on the impersonated// identity, we must instantiate it under app-level impersonation.using (new ApplicationImpersonationContext()) {return DataProtector.Create(dataProtectorTypeName, applicationName, purpose.PrimaryPurpose, purpose.SpecificPurposes);}};// 删减验证factory的部分代码和try-catchreturn factory; // we know at this point the factory is good}
}

其原始代码如下:https://referencesource.microsoft.com/#System.Web/Security/Cryptography/MachineKeyDataProtectorFactory.cs,cc110253450fcb16

注意 _machineKeySection的 ApplicationName和 DataProtectorType默认都是空字符串 "",具体不细说,在这定义的:https://referencesource.microsoft.com/#System.Web/Configuration/MachineKeySection.cs,50

所以我们继续看 DataProtector的代码:

public abstract class DataProtector
{public static DataProtector Create(string providerClass,string applicationName,string primaryPurpose,params string[] specificPurposes){// Make sure providerClass is not null - Other parameters checked in constructorif (null == providerClass)throw new ArgumentNullException("providerClass");// Create a DataProtector based on this type using CryptoConfigreturn (DataProtector)CryptoConfig.CreateFromName(providerClass, applicationName, primaryPurpose, specificPurposes);}
}

注意它唯一的引用 CryptoConfig,已经属于 .NETCore已经包含的范畴了,因此没必要继续深入追踪。

Purpose

注意一开始时,我们说到的 Purpose,相关定义如下:

public class Purpose {// ...有删减public static readonly Purpose User_MachineKey_Protect = new Purpose("User.MachineKey.Protect");internal Purpose AppendSpecificPurposes(IList<string> specificPurposes){if (specificPurposes == null || specificPurposes.Count == 0){return this;}string[] array = new string[SpecificPurposes.Length + specificPurposes.Count];Array.Copy(SpecificPurposes, array, SpecificPurposes.Length);specificPurposes.CopyTo(array, SpecificPurposes.Length);return new Purpose(PrimaryPurpose, array);}// Returns a label and context suitable for passing into the SP800-108 KDF.internal void GetKeyDerivationParameters(out byte[] label, out byte[] context) {// The primary purpose can just be used as the label directly, since ASP.NET// is always in full control of the primary purpose (it's never user-specified).if (_derivedKeyLabel == null) {_derivedKeyLabel = CryptoUtil.SecureUTF8Encoding.GetBytes(PrimaryPurpose);}// The specific purposes (which can contain nonce, identity, etc.) are concatenated// together to form the context. The BinaryWriter class prepends each element with// a 7-bit encoded length to guarantee uniqueness.if (_derivedKeyContext == null) {using (MemoryStream stream = new MemoryStream())using (BinaryWriter writer = new BinaryWriter(stream, CryptoUtil.SecureUTF8Encoding)) {foreach (string specificPurpose in SpecificPurposes) {writer.Write(specificPurpose);}_derivedKeyContext = stream.ToArray();}}label = _derivedKeyLabel;context = _derivedKeyContext;}
}

注意其 PrimaryPurpose值为: "User.MachineKey.Protect"

另外还需要记住这个 GetKeyDerivationParameters方法,它将在接下来的 SP800_108类中使用,它将 PrimaryPurpose经过 utf8编码生成 label参数,然后用所有的 SpecificPurposes通过二进制序列化,生成 context参数。

原始代码链接:https://referencesource.microsoft.com/#System.Web/Security/Cryptography/Purpose.cs,6fd5fbe04ec71877

SP800_108

已经接近尾声了,我们知道一个字符串要转换为密钥,就必须经过一个安全的哈希算法。之前我们接触得最多的,是 Rfc2898DeriveBytes,但它是为了保存密码而设计的。这里不需要这么复杂,因此…… .NET另写了一个。

这个类代码非常长,但好在它所有内容都兼容 .NETCore,因此可以直接复制粘贴。

它的目的是通过 Purpose来生成密钥。有兴趣的读者可以了解一下其算法:https://referencesource.microsoft.com/#System.Web/Security/Cryptography/SP800_108.cs,38

收尾

关系图整理

我已经尽力将代码重点划出来,但仍然很复杂。这么多类,我最后理了一个关系图,用于了解其调用、依赖链:

MachineKeyPurposeAspNetCryptoServiceProviderMachineKeySectionMachineKeyCryptoAlgorithmFactoryCryptoAlgorithmsMachineKeyMasterKeyProviderCryptographicKeyMachineKeyDataProtectorFactoryDataProtectorCryptoConfigSP800_108

祖传代码

整理了这么久,没有点干货怎么能行?基于以上的整理,我写了一份“祖传代码”,可以直接拿来在 .NETCore中使用。代码较长,约 200行,已经上传到我的博客数据网站,各位可以自取:https://github.com/sdcb/blog-data/tree/master/2020/20200222-machinekey-in-dotnetcore

其实只要一行代码?

直到后来,我发现有人将这些功能封闭成了一个 NuGet包: AspNetTicketBridge,只需“一行”代码,就能搞定所有这些功能:

// https://github.com/dmarlow/AspNetTicketBridge
string cookie = "你的Cookie内容";
string validationKey = "machineKey中的validationKey";
string decryptionKey = "machineKey中的decryptionKey";
OwinAuthenticationTicket ticket = MachineKeyTicketUnprotector.UnprotectCookie(cookie, decryptionKey, validationKey);

用 LINQPad运行,结果如下(完美破解): 

总结

喜欢的朋友请关注我的微信公众号:【DotNet骚操作】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311612.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cg word List 1

cg word List 1 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢!

[蓝桥杯2016初赛]剪邮票-dfs+next_permutation(好题)

题目描述 如下图, 有12张连在一起的12生肖的邮票。现在你要从中剪下5张来&#xff0c;要求必须是连着的。&#xff08;仅仅连接一个角不算相连&#xff09; 比如&#xff0c;下面两张图中&#xff0c;粉红色所示部分就是合格的剪取。 请你计算&#xff0c;一共有多少种不同…

cg word List2

cg word List 2 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

如何扩展分布式日志组件(Exceptionless)的日志通知?

作者&#xff1a;justmine头条号&#xff1a;大数据与云原生微信公众号&#xff1a;大数据与云原生创作不易&#xff0c;在满足创作共用版权协议的基础上可以转载&#xff0c;但请以超链接形式注明出处。为了方便阅读&#xff0c;微信公众号已按分类排版&#xff0c;后续的文章…

hadoop hive集群_基于伪分布式Hadoop搭建Hive平台详细教程

一、搭建环境的前提条件环境&#xff1a;Linux系统Hadoop-2.6.0MySQL 5.6apache-hive-2.3.7这里的环境不一定需要和我一样&#xff0c;基本版本差不多都ok的&#xff0c;所需安装包和压缩包自行下载即可。但是注意hive和hadoop都是2.x系列版本的。这里提供一个我下载的hive版本…

cg word List 3

cg word List 3 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

.NET Core开发实战(第7课:用Autofac增强容器能力)--学习笔记(上)

07 | 用Autofac增强容器能力&#xff1a;引入面向切面编程&#xff08;AOP&#xff09;的能力这一节讲解使用第三方框架来扩展依赖注入容器什么情况下需要我们引入第三方容器组件&#xff1f;大部分情况下&#xff0c;默认的容器组件足够使用当需要一些非常特殊的场景如下&…

sql 对groupby 后的数据limit_SQL(三)——汇总分析

1. 汇总分析函数的3个功能&#xff1a;①功能&#xff1b;②输入&#xff08;参数&#xff09;&#xff1b;③输出&#xff08;返回值&#xff09;查询课程编号为“0002”的总成绩&#xff1a;查询选了课程的学生人数&#xff1a;2.分组sql分组:group bygroup by 实现数据分组&…

cg word List4

cg word List4 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

通过 Serverless 加速 Blazor WebAssembly

Blazor ❤ Serverless我正在开发 Ant Design 的 Blazor 版本&#xff0c;预览页面部署在 Github Pages 上&#xff0c;但是加载速度很不理想&#xff0c;往往需要 1 分钟多钟才完成。项目地址&#xff1a;https://github.com/ElderJames/ant-design-blazor[1] 求 Star。当寻求解…

eclipse中tomcat启动不了_Eclipse怎样与Tomcat集成

1 打开eclipse2 打开Window -> Show View -> Servers3 点击No servers are available.Click this link to create a new server...4 选择 Apache -> Tomcat v9.0 Server&#xff0c;tomcat的版本可以根据本机安装的tomcat版本选择&#xff0c;然后点击Next5 Name不需要…

树的存储结构-双亲表示法

特点:找双亲容易&#xff0c;找孩子难 代码如下&#xff1a; #include <iostream> using namespace std; typedef char ElemType; #define MAX_Tree_size 100 typedef struct PTNode {ElemType data;int parent;//双亲位置域 }PTNode;typedef struct {PTNode nodes[MA…

cg word List5

## cg word List5 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

x509trustmanager怎么验证证书_GeoTrust通配符证书和多域名SSL证书对比评测

GeoTrust SSL证书类型很丰富&#xff0c;能够满足多样的需求&#xff0c;而GeoTrust通配符证书和多域名SSL证书都是支持多个域名的SSL证书&#xff0c;这时候就有网友问了&#xff0c;GeoTrust通配符证书和多域名SSL证书选择哪个好呢&#xff1f;接下来安信证书就为大家做个详细…

树的存储结构-孩子链表

特点:找孩子容易&#xff0c;找双亲难 代码如下&#xff1a; #include <iostream> using namespace std; typedef char ElemType; #define MAX_tree_size //孩子结构 typedef struct CTNode {int child;struct CTNode *next; }*ChildPtr; //双亲结点结构 typedef stru…

【视频教程】使用 ASP.NET Core 3.x 构建 RESTful Web API 已完结

使用 ASP.NET Core 3.x 构建 RESTful Web API 的视频教程已经完结&#xff0c;共50讲&#xff0c;约10.5小时。B站可看&#xff0c;点击原文链接。度娘盘可下载完整视频&#xff1a;https://pan.baidu.com/s/1VPE3bkAVQI_RO3tDrxod4w提取码: hsmj源码以及PPT&#xff1a;https:…

ASP.NET Core 借助 Helm 部署应用至 K8S

前言玩K8S也有一段时间了&#xff0c;借助云服务提供商的K8S控制台&#xff0c;已经可以很方便的快速部署应用至K8S。通过简单的点击&#xff0c;可以一次性帮忙创建K8S 对象&#xff1a;Deployment、Service、Ingress、ConfigMap等。但是当服务的规模上来后&#xff0c;这种方…

.Net Core中IOC容器的使用

本文由江北原创投稿&#xff0c;独家授权技术在于分享&#xff08;公众号ID&#xff1a;sharecore&#xff09;&#xff0c;未经许可&#xff0c;不得转载。作者&#xff1a;江北打代码之前先说一下几个概念,那就是什么是IOC、DI、DIP虽然网上讲这些的已经有很多了,我这里还是要…

qiankun 微前端_qiankun 微前端应用实践与部署(二)

下面是两种方案的简要描述。传统部署方式通过配置 nginx 端口到目录的转发。具体可查看上一篇文章特点需要对外开放子应用对应的端口&#xff0c;将编译好的应用文件放到对应的配置目录。docker 部署方式首先构建主应用与子应用的 docker 镜像&#xff0c;通过 docker run 或者…