亲宝软件园·资讯

展开

在.NET Core中使用MachineKey

.NET骚操作 人气:2

在.NET Core中使用MachineKey

姐妹篇:《ASP.NET Cookie是怎么生成的》
姐妹篇:《.NET Core验证ASP.NET密码》

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

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

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

认识MachineKey

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

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

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

MachineKeyvalidationKeydecryptionKey的内容只要符合长度和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,它使用了MachineKeyCryptoAlgorithmFactoryMachineKeyMasterKeyProviderMachineKeyDataProtectorFactory,以及一个看上去有点奇怪的SP800_108.DeriveKey

AspNetCryptoServiceProviderGetCryptoService方法如下:

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 <machineKey> algorithms for cryptography
        cryptoService = GetNetFXCryptoService(purpose, options);
    }

    // always homogenize errors returned from the crypto service
    return new HomogenizingCryptoServiceWrapper(cryptoService);
}

private NetFXCryptoService GetNetFXCryptoService(Purpose purpose, CryptoServiceOptions options) {
    // Extract the encryption and validation keys from the provided Purpose object
    CryptographicKey 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,这两个就是之前所说的MachineKeyMasterKeyProviderMachineKeyDataProtectorFactory

注意其中还有一个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 clearData
    public byte[] Unprotect(byte[] protectedData) {
        // ...有删减
        using (SymmetricAlgorithm decryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
            // 省略约100行代码

加载全部内容

相关教程
猜你喜欢
用户评论