网站建设风格,网站优化方案书,wordpress 申请表单,南通快速建站公司在.NET Core中使用MachineKey在上篇文章中#xff0c;我介绍了 Cookie是基于 MachineKey生成的#xff0c; MachineKey决定了 Cookie生成的算法和密钥#xff0c;并如果使用多台服务器做负载均衡时#xff0c;必须指定一致的 MachineKey。但在 .NETCore中#xff0c;官方似… 在.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 validationKey128个hex字符 decryptionKey64个hex字符 validationSHA1 decryptionAES /
网上能找到可以直接生成随机 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 its 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 LazyAspNetCryptoServiceProvider _singleton new LazyAspNetCryptoServiceProvider(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 its configured and the caller didnt 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.config/ web.config中读取两个 xml位置的值并转换为 CryptographicKey然后 CryptographicKey的本质就是一个字节数组 byte[]。注意原版的 GenerateCrytographicKey函数其实很长但重点确实就是前面这三行代码后面的是一些骚操作可以自动从一些配置的位置生成 machineKey这应该和 machineKey节点缺失或者不写有关不在本文考虑的范畴以内。有兴趣的读者可以参见原始代码https://referencesource.microsoft.com/#system.web/Security/Cryptography/MachineKeyMasterKeyProvider.cs,87MachineKeyDataProtectorFactory其源代码如下有删减internal sealed class MachineKeyDataProtectorFactory : IDataProtectorFactory {public DataProtector GetDataProtector(Purpose purpose) {if (_dataProtectorFactory null) {_dataProtectorFactory GetDataProtectorFactory();}return _dataProtectorFactory(purpose);}private FuncPurpose, DataProtector GetDataProtectorFactory() {string applicationName _machineKeySection.ApplicationName;string dataProtectorTypeName _machineKeySection.DataProtectorType;FuncPurpose, 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(IListstring 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 (its 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,6fd5fbe04ec71877SP800_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骚操作】