在.NET Core中使用MachineKey
2021-06-04 04:01
标签:transform dev 整合 验证 array 关系 ash 参数 compute 姐妹篇:《ASP.NET Cookie是怎么生成的》 在上篇文章中,我介绍了 但在 今天我将深入探索 在 网上能找到可以直接生成随机 但 打开 具体代码可参见:https://referencesource.microsoft.com/#system.web/Security/MachineKey.cs,209 可见它本质是使用了 其中 具体代码可见:https://referencesource.microsoft.com/#system.web/Security/Cryptography/AspNetCryptoServiceProvider.cs,68dbd1c184ea4e88 可见它本质是依赖于 注意其中有一个判断,我结合 然后 注意其中还有一个 最后调用 这个是重点了,源代码如下(有删减): 这个代码非常长,我直接一刀全部删减了,只保留注释。如果不理解先好好看注释,不理解它在干嘛,直接看代码可能非常难,有兴趣的可以直接先看看代码:https://referencesource.microsoft.com/#system.web/Security/Cryptography/NetFXCryptoService.cs,35 首先看注释: 加密之后的数据由 其中 现在再来看看代码: 下文就应该是轻车熟路,依葫芦画瓢了,先验证签名: 然后直接解密: 可见这个类都是一些“正常操作”。之后我们来补充一下遗漏的部分。 首先是 源代码链接在这:https://referencesource.microsoft.com/#system.web/Security/Cryptography/MachineKeyCryptoAlgorithmFactory.cs,14 可见非常地直白、浅显易懂。 然后是 可见这个类就是从 注意,原版的 其源代码如下(有删减): 其原始代码如下:https://referencesource.microsoft.com/#System.Web/Security/Cryptography/MachineKeyDataProtectorFactory.cs,cc110253450fcb16 注意 所以我们继续看 注意它唯一的引用 注意一开始时,我们说到的 注意其 另外还需要记住这个 原始代码链接:https://referencesource.microsoft.com/#System.Web/Security/Cryptography/Purpose.cs,6fd5fbe04ec71877 已经接近尾声了,我们知道一个字符串要转换为密钥,就必须经过一个安全的哈希算法。之前我们接触得最多的,是 这个类代码非常长,但好在它所有内容都兼容 它的目的是通过 我已经尽力将代码重点划出来,但仍然很复杂。这么多类,我最后理了一个关系图,用于了解其调用、依赖链: 整理了这么久,没有点干货怎么能行?基于以上的整理,我写了一份“祖传代码”,可以直接拿来在 直到后来,我发现有人将这些功能封闭成了一个 用 喜欢的朋友请关注我的微信公众号:【DotNet骚操作】 在.NET Core中使用MachineKey 标签:transform dev 整合 验证 array 关系 ash 参数 compute 原文地址:https://www.cnblogs.com/sdflysha/p/20200222-machingkey-in-dotnetcore.html在.NET Core中使用MachineKey
姐妹篇:《.NET Core验证ASP.NET密码》Cookie
是基于MachineKey
生成的,MachineKey
决定了Cookie
生成的算法和密钥,并如果使用多台服务器做负载均衡时,必须指定一致的MachineKey
。.NET Core
中,官方似乎并没有提供MachineKey
实现,这为兼容.NET Framework
的Cookie
造成了许多障碍。MachineKey
这个类,看看里面到底藏了什么东西,本文的最后我将使用.NET Core
来解密一个ASP.NET MVC
生成的Cookie
。认识MachineKey
.NET Framework
中,machineKey
首先需要一个配置,写在app.config
或者web.config
中,格式一般如下:
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);
}
}
AspNetCryptoServiceProvider.Instance
,然后调用其GetCryptoService
方法,然后获取一个cryptoService
,最后调用Unprotect
,注意其中还使用了一个Purpose
的类,依赖非常多。AspNetCryptoServiceProvider
AspNetCryptoServiceProvider.Instance
的定义如下(有删减和整合):internal sealed class AspNetCryptoServiceProvider : ICryptoServiceProvider {
private static readonly Lazy _singleton = new Lazy(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);
}
}
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 cacheability
cryptoService = GetDataProtectorCryptoService(purpose);
}
else {
// Otherwise we fall back to using the
dnSpy
做了认真的调试,发现它默认走的是GetNetFXCryptoService
,也就是注释中所谓的
算法。GetNetFXCryptoService
方法依赖于_masterKeyProvider
和_keyDerivationFunction
用来生成两个CryptographicKey
,这两个就是之前所说的MachineKeyMasterKeyProvider
和MachineKeyDataProtectorFactory
。HomogenizingCryptoServiceWrapper
类,故名思义,它的作用应该是统一管理加密解释过程中的报错,实际也确实如此,我不作深入,有兴趣的读者可以看看原始代码在这:https://referencesource.microsoft.com/#system.web/Security/Cryptography/HomogenizingCryptoServiceWrapper.cs,25NetFXCryptoService
来执行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 clearData
public byte[] Unprotect(byte[] protectedData) {
// ...有删减
using (SymmetricAlgorithm decryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
// 省略约100行代码??
}
}
}
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 := clearData
byte[] clearData = memStream.ToArray();
return clearData;
}
}
}
MachineKeyCryptoAlgorithmFactory
MachineKeyCryptoAlgorithmFactory
,代码如下(只保留了重点):switch (algorithmName) {
case "AES":
case "Auto": // currently "Auto" defaults to AES
return 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
}
MachineKeyMasterKeyProvider
MachineKeyMasterKeyProvider
,核心代码如下:private CryptographicKey GenerateCryptographicKey(string configAttributeName, string configAttributeValue, int autogenKeyOffset, int autogenKeyCount, string errorResourceString) {
byte[] keyMaterial = CryptoUtil.HexToBinary(configAttributeValue);
// If
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 Func
_machineKeySection
的ApplicationName
和DataProtectorType
默认都是空字符串""
,具体不细说,在这定义的:https://referencesource.microsoft.com/#System.Web/Configuration/MachineKeySection.cs,50DataProtector
的代码: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 constructor
if (null == providerClass)
throw new ArgumentNullException("providerClass");
// Create a DataProtector based on this type using CryptoConfig
return (DataProtector)CryptoConfig.CreateFromName(providerClass, applicationName, primaryPurpose, specificPurposes);
}
}
CryptoConfig
,已经属于.NET Core
已经包含的范畴了,因此没必要继续深入追踪。Purpose
Purpose
,相关定义如下:public class Purpose {
// ...有删减
public static readonly Purpose User_MachineKey_Protect = new Purpose("User.MachineKey.Protect");
internal Purpose AppendSpecificPurposes(IList
PrimaryPurpose
值为:"User.MachineKey.Protect"
。GetKeyDerivationParameters
方法,它将在接下来的SP800_108
类中使用,它将PrimaryPurpose
经过utf8
编码生成label
参数,然后用所有的SpecificPurposes
通过二进制序列化,生成context
参数。SP800_108
Rfc2898DeriveBytes
,但它是为了保存密码而设计的。这里不需要这么复杂,因此…….NET
另写了一个。.NET Core
,因此可以直接复制粘贴。Purpose
来生成密钥。有兴趣的读者可以了解一下其算法:https://referencesource.microsoft.com/#System.Web/Security/Cryptography/SP800_108.cs,38收尾
关系图整理
MachineKey
Purpose
AspNetCryptoServiceProvider
MachineKeySection
MachineKeyCryptoAlgorithmFactory
CryptoAlgorithms
MachineKeyMasterKeyProvider
CryptographicKey
MachineKeyDataProtectorFactory
DataProtector
CryptoConfig
SP800_108
祖传代码
.NET Core
中使用。代码较长,约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
运行,结果如下(完美破解):总结
文章标题:在.NET Core中使用MachineKey
文章链接:http://soscw.com/index.php/essay/90232.html