从已部署的 Azure 应用服务中提取 MachineKey

Extracting the MachineKey from a deployed Azure App Service

我有一个 ASP.NET 4.6 Web API 服务 运行 作为单个区域中单个应用服务计划中的 Azure 应用服务。我们正在修改此服务以部署在前面有负载均衡器的多个区域,每个区域都有自己的应用程序服务计划。因此,我们需要确保在每个应用服务计划上使用相同的机器密钥,以防止用户在负载均衡器定向到不同服务器时被注销。

我们的应用程序 运行 使用 Azure 在单个应用程序服务计划中自动提供的机器密钥已经有一段时间了。为了避免导致我们所有的客户在过渡期间注销,我计划提取这个现有的机器密钥,然后将其部署到其他地区的新应用程序服务计划中。听起来很简单,对吧?

然而,事实证明提取此密钥是一项挑战。

我已经尝试过此处列出的解决方案:Getting the current ASP.NET machine key

虽然每种方法都会 return 某种密钥,但该密钥似乎与实际用于生成不记名令牌或保护刷新令牌票证的密钥不匹配。当我将这些密钥部署到其他服务器时,不记名令牌仍然被视为无效并且尝试使用现有的刷新令牌会导致 invalid_grant 响应。

此外,即使我在 web.config 中手动设置机器密钥(或在运行时使用 this 等代码),提取的机器密钥的 none 与我手动设置的机器密钥,提供了进一步的证据,证明它们 returning 不是实际使用的机器密钥。在我的本地开发机器和 Azure 中都是如此。

作为参考,这是我用来以三种不同方式提取解密和验证密钥的代码(删除了一些安全代码):

[DllImport(@"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll")]
internal static extern int EcbCallISAPI(IntPtr pECB, int iFunction, byte[] bufferIn, int sizeIn, byte[] bufferOut, int sizeOut);

[Route("machine-key-test")]
public async Task<JObject> GetMachineKeys()
{
    return new JObject(
        new JProperty("A", GetAdminData()),
        new JProperty("B", GetAdminDataNoIsolateApps()),
        new JProperty("C", GetAdminDataPre45()));

    JObject GetAdminData()
    {
        string appPath = "/";
        byte[] genKeys = new byte[1024];
        byte[] autogenKeys = new byte[1024];

        int res = EcbCallISAPI(IntPtr.Zero, 4, genKeys, genKeys.Length, autogenKeys, autogenKeys.Length);

        if (res == 1)
        {
            // Same as above
            int validationKeySize = 64;
            int decryptionKeySize = 24;

            byte[] validationKey = new byte[validationKeySize];
            byte[] decryptionKey = new byte[decryptionKeySize];

            Buffer.BlockCopy(autogenKeys, 0, validationKey, 0, validationKeySize);
            Buffer.BlockCopy(autogenKeys, validationKeySize, decryptionKey, 0, decryptionKeySize);

            int pathHash = StringComparer.InvariantCultureIgnoreCase.GetHashCode(appPath);
            validationKey[0] = (byte)(pathHash & 0xff);
            validationKey[1] = (byte)((pathHash & 0xff00) >> 8);
            validationKey[2] = (byte)((pathHash & 0xff0000) >> 16);
            validationKey[3] = (byte)((pathHash & 0xff000000) >> 24);

            decryptionKey[0] = (byte)(pathHash & 0xff);
            decryptionKey[1] = (byte)((pathHash & 0xff00) >> 8);
            decryptionKey[2] = (byte)((pathHash & 0xff0000) >> 16);
            decryptionKey[3] = (byte)((pathHash & 0xff000000) >> 24);

            var decrptionKeyString = decryptionKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
            var validationKeyString = validationKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());

            return new JObject(
                new JProperty("D", decrptionKeyString),
                new JProperty("V", validationKeyString));
        }


        return null;
    }

    JObject GetAdminDataNoIsolateApps()
    {
        string appPath = "/";
        byte[] genKeys = new byte[1024];
        byte[] autogenKeys = new byte[1024];

        int res = EcbCallISAPI(IntPtr.Zero, 4, genKeys, genKeys.Length, autogenKeys, autogenKeys.Length);

        if (res == 1)
        {
            // Same as above
            int validationKeySize = 64;
            int decryptionKeySize = 24;

            byte[] validationKey = new byte[validationKeySize];
            byte[] decryptionKey = new byte[decryptionKeySize];

            Buffer.BlockCopy(autogenKeys, 0, validationKey, 0, validationKeySize);
            Buffer.BlockCopy(autogenKeys, validationKeySize, decryptionKey, 0, decryptionKeySize);

            var decrptionKeyString = decryptionKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
            var validationKeyString = validationKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());

            return new JObject(
                new JProperty("D", decrptionKeyString),
                new JProperty("V", validationKeyString));
        }


        return null;
    }

    JObject GetAdminDataPre45()
    {
    // 
        byte[] autogenKeys = (byte[]) typeof(HttpRuntime)
            .GetField("s_autogenKeys", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);

        Type t = typeof(System.Web.Security.DefaultAuthenticationEventArgs).Assembly.GetType(
            "System.Web.Security.Cryptography.MachineKeyMasterKeyProvider");
        ConstructorInfo ctor = t.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];

        Type ckey = typeof(System.Web.Security.DefaultAuthenticationEventArgs).Assembly.GetType(
            "System.Web.Security.Cryptography.CryptographicKey");
        ConstructorInfo ckeyCtor = ckey.GetConstructors(BindingFlags.Instance | BindingFlags.Public)[0];
        Object ckeyobj = ckeyCtor.Invoke(new object[] {autogenKeys});
        object o = ctor.Invoke(new object[] {new MachineKeySection(), null, null, ckeyobj, null});
        var encKey = t.GetMethod("GetEncryptionKey").Invoke(o, null);
        byte[] encBytes = ckey.GetMethod("GetKeyMaterial").Invoke(encKey, null) as byte[];
        var vldKey = t.GetMethod("GetValidationKey").Invoke(o, null);
        byte[] vldBytes = ckey.GetMethod("GetKeyMaterial").Invoke(vldKey, null) as byte[];
        string decryptionKey = BitConverter.ToString(encBytes);
        decryptionKey = decryptionKey.Replace("-", "");
        string validationKey = BitConverter.ToString(vldBytes);
        validationKey = validationKey.Replace("-", "");

        return new JObject(
            new JProperty("D", decryptionKey),
            new JProperty("V", validationKey));
    }
}

这是我得到的输出示例:

{
    "A": {
        "D": "b298ba4ef5e8421e178770f50ee5414dd0aa1698afc3169d",
        "V": "b298ba4e3ed466051c60e4c8646ece2546f27e8b9b2e9a569453eaab6b2a4e93bc08a3171ea61972adfd83c97d21bbcfad2acd3e6d35668f5458f8d7c8f55913"
    },
    "B": {
        "D": "dc509c9af5e8421e178770f50ee5414dd0aa1698afc3169d",
        "V": "84246e973ed466051c60e4c8646ece2546f27e8b9b2e9a569453eaab6b2a4e93bc08a3171ea61972adfd83c97d21bbcfad2acd3e6d35668f5458f8d7c8f55913"
    },
    "C": {
        "D": "A2EDFD4ECE75A91F8E38D62B569248B14CE9193DD42E543E0D4BA5C9E2BED912",
        "V": "DC6144A79985DEF712FABC729871A79FF2CF0DD73CBA617C3764D234DA1B63AD"
    }
}

我试过依次使用每组密钥,既没有明确设置解密和验证算法,也尝试指定与定义的密钥长度相匹配的各种算法组合 here,运气不好。

正如我所说,这些密钥中的 none 将匹配我在 web.config 中手动设置的机器密钥,或者我在代码中手动设置的机器密钥。

我得出的结论是,我要么做错了一些微不足道的错误,要么我将不得不通过将机器密钥更改为新密钥来强制注销所有用户我们所有的服务器。我希望有人能给我指出正确的方向。

事实证明我把整个事情复杂化了。

如果您使用 Kudu(来自 Azure 门户中相关应用服务的“高级工具”部分),那么您可以在 D:\local\Config\rootweb.config 找到一个包含机器密钥的文件。

感谢一个不相关问题的答案,我找到了它 here,所以希望这会减轻其他人的痛苦。