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

Jam*_*ley 2 asp.net machinekey azure-app-service-plans azure-web-app-service

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

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

然而提取这个密钥被证明是一个挑战。

我已经尝试过此处列出的解决方案:获取当前的 ASP.NET 计算机密钥

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

此外,即使我在 web.config 中手动设置机器密钥(或在运行时使用此类代码,提取的机器密钥也不会与我手动设置的机器密钥匹配,这提供了进一步的证据,表明它们是什么返回的不是实际使用的机器密钥。在我的本地开发计算机和 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()
    {
    // /sf/answers/2516803761/
        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));
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我得到的输出的示例:

{
    "A": {
        "D": "b298ba4ef5e8421e178770f50ee5414dd0aa1698afc3169d",
        "V": "b298ba4e3ed466051c60e4c8646ece2546f27e8b9b2e9a569453eaab6b2a4e93bc08a3171ea61972adfd83c97d21bbcfad2acd3e6d35668f5458f8d7c8f55913"
    },
    "B": {
        "D": "dc509c9af5e8421e178770f50ee5414dd0aa1698afc3169d",
        "V": "84246e973ed466051c60e4c8646ece2546f27e8b9b2e9a569453eaab6b2a4e93bc08a3171ea61972adfd83c97d21bbcfad2acd3e6d35668f5458f8d7c8f55913"
    },
    "C": {
        "D": "A2EDFD4ECE75A91F8E38D62B569248B14CE9193DD42E543E0D4BA5C9E2BED912",
        "V": "DC6144A79985DEF712FABC729871A79FF2CF0DD73CBA617C3764D234DA1B63AD"
    }
}
Run Code Online (Sandbox Code Playgroud)

我尝试依次使用每组密钥,既没有显式设置解密和验证算法,也没有指定与此处定义的密钥长度相匹配的各种算法组合,但没有成功。

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

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

Jam*_*ley 7

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

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

我找到它要感谢这里一个不相关问题的答案,所以希望这能为其他人减轻一些痛苦。