获取当前ASP.NET机器密钥

24

我需要获取当前应用程序的ASP.NET机器密钥。当然,如果在配置文件中指定了机器密钥,则很容易实现此目标;但是,如果设置为自动生成,则似乎没有任何公共方法可用于获取它。

基本上,我想要它以便我可以像ASP.NET表单身份验证提供程序一样编写加密/MAC cookie。

是否有人有任何指针或想法?

9个回答

19

好奇先生也对获取机器密钥很感兴趣。 MachineKeySection 上的属性没有用,因为它们在初始化之后被清零, 这发生在您可以使用反射读取它们之前。

在当前的4.5框架中进行了一些挖掘,结果发现自动生成的密钥存储在HttpApplication.s_autogenKeys字节数组中。 验证密钥是前64个字节,后面是24个字节的解密密钥。

如果您没有选择4.5框架中的新加密功能,也就是说,您没有在web.config中设置<httpRuntime targetFramework="4.5">(如果您使用的是以前版本的框架创建的应用程序,则是这种情况),则可以像这样访问密钥:

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

        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);

        // This is the IsolateApps bit, which is set for both keys
        int pathHash = StringComparer.InvariantCultureIgnoreCase.GetHashCode(HttpRuntime.AppDomainAppVirtualPath);
        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);

两个键的默认设置是AutoGenerate,IsolateApps; IsolateApps位需要将应用程序路径哈希的前四个字节复制到键的开头。

如果您选择加入fx4.5中的密码学改进, 那么您需要在MachineKeyMasterKeyProvider中查找有效的密钥。

获取没有HttpApplication的密钥

HttpApplication通过从SetAutogenKeys()调用webengine4.dll中的本机方法来获取其密钥。我们也可以调用DLL自己获取密钥。我们只需要知道我们的应用程序路径即可。

假设我们想要获取根应用程序"/"的自动生成密钥。

使用LinqPad:

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

void Main()
{
    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);

        Console.WriteLine("DecryptionKey: {0}", decryptionKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString()));
        Console.WriteLine("ValidationKey: {0}", validationKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString()));
    }
}

从MachineKeyMasterKeyProvider获取密钥

通过使用内部构造函数实例化MachineKeyMasterKeyProvider,并将在上面的代码中获取的autogenKeys 字节数组传递给它,即可访问新的fx4.5内容的密钥。提供程序有GetEncryptionKeyGetValidationKey方法以获取实际密钥。


我应该注意到,我的目标是能够提取自动生成的密钥或有效的派生密钥,并将其设置在新服务器上,以便现有令牌可以验证。 - thebringking
我的方法是从 .net framework source 中复制相关的方法到一个新项目中,然后将其精简到最小限度,以加密/解密与网络嗅探器获取的相同的 cookie。 - Mr. Curious
@thebringking 我会先重新创建一个简化版本的 AspNetCryptoServiceProvider,因为它似乎包含了获取密钥的调用,然后从框架源代码中复制方法,直到我有一个独立的解决方案。如果你解决了这个问题,请在这里发布答案,也许其他人会发现它有用。 - Mr. Curious
@thebringking,我已经更新了答案,并提供了一个提示,以从“MachineKeyMasterKeyProvider”获取密钥。希望能有所帮助。 - Mr. Curious
太好了!我使用代码从我的实时网站(在www.子域下运行)中提取密钥,并将它们复制到我的测试网站(在beta.子域下运行)的web.config中的machineKeys元素中。现在,在实时网站上登录也会让我在我的测试网站上登录。感谢您的分享! - Fredrik Johansson
显示剩余3条评论

7

对于 .Net 4.5,以下是代码:

//using System.Reflection
//using System.Web.Configuration

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("-", "");

这对我有用,而不是被接受的答案(使用情况:我需要在web.config中硬编码现有的machinekey,以便用户无需重新登录应用程序) - Alex from Jitbit

6

如果您正在使用.NET 4,那么有一个名为MachineKey的类。它不会直接访问实际密钥,但它提供了使用与FormsAuthentication类相同的算法进行数据编码和解码的方法,以及添加HMAC验证选项的方法。


3
感谢您,Curious先生,
根据您的提示,我得到了以下内容:
private byte[] _validationKey;
private byte[] _decryptionKey;

public static byte[] GetKey(object provider, string name)
{
  var validationKey = provider.GetType().GetMethod(name).Invoke(provider, new object[0]);
  return (byte[])validationKey.GetType().GetMethod("GetKeyMaterial").Invoke(validationKey, new object[0]);
}

protected override void OnLoad(EventArgs e)
{
    var machineKey = typeof(MachineKeySection).GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Single(a => a.Name == "GetApplicationConfig").Invoke(null, new object[0]);

    var type = Assembly.Load("System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a").GetTypes().Single(a => a.Name == "MachineKeyMasterKeyProvider");

    var instance = type.Assembly.CreateInstance(
        type.FullName, false,
        BindingFlags.Instance | BindingFlags.NonPublic,
        null, new object[] { machineKey, null, null, null, null }, null, null);

    var validationKey = type.GetMethod("GetValidationKey").Invoke(instance, new object[0]);
    var key = (byte[])validationKey.GetType().GetMethod("GetKeyMaterial").Invoke(validationKey, new object[0]);


    _validationKey = GetKey(instance, "GetValidationKey");
    _decryptionKey = GetKey(instance, "GetEncryptionKey");
}

1
对于4.5版本,这是正确的答案。我没有逐字尝试过这段代码,但在看到这个答案之前,我想出了一个类似的解决方案。我还希望能够设置密钥而不将其提交到web.config中,完整的解决方案在这里:https://gist.github.com/cmcnab/d2bbed02eb429098ed3656a0729ee40a - Chadwick

1
我结合了上面针对.NET 4.5的答案,想到了以下方法。将下面的代码放入名为mk.aspx的文件中,然后浏览它以获取密钥。一定要立即删除它,因为这是恶意的。
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Web" %>
<%@ Import Namespace="System.Web.Configuration" %>
<%@ Page Language="C#"%>
<%
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("-", "");
%>

<machineKey
validationKey="<%=validationKey%>"
decryptionKey="<%=decryptionKey%>"
/>

1

Forms身份验证提供程序可以访问它,因为有内部方法允许它 :) - blowdart
我认为不可能重现这些方法,因为那些内部方法的源代码是不可用的? - Tom Robinson
1
它们可以使用,但是自动生成的MAC密钥的实际后备存储器不可用,因此即使使用了反射器、剪切和粘贴方法,实际密钥本身似乎也无法访问。这让我想知道是否有什么我错过了! - blowdart

0
我曾经遇到同样的问题,需要从一个正在运行的 Web 应用程序中获取 machinekey(而不使用 .NET 4.5 加密功能),但我无法对其进行代码更改,因此我创建了一个简单的 .aspx 文件来提取密钥并将其转储到文件中,然后将其放置在应用程序根目录中,并使用浏览器访问它(无需触摸正在运行的应用程序)。
<%@ Page Language="C#"
var runTimeType = typeof(System.Web.HttpRuntime);
var autogenKeysField = runTimeType.GetField("s_autogenKeys", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
var autogenKeys = (byte[])autogenKeysField.GetValue(null);
var machineKeySection = new System.Web.Configuration.MachineKeySection();

var autogenKeyProperty = typeof(System.Web.Configuration.MachineKeySection).GetProperty("AutogenKey", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
var decryptionKeyField = typeof(System.Web.Configuration.MachineKeySection).GetField("_DecryptionKey", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
var validationKeyField = typeof(System.Web.Configuration.MachineKeySection).GetField("_ValidationKey", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

// This needs to be done to make machineKeySection refresh it's data
var touch = (bool)autogenKeyProperty.GetValue(machineKeySection);
var decryptionKey = (byte[])decryptionKeyField.GetValue(machineKeySection);
var validationKey = (byte[])validationKeyField.GetValue(machineKeySection);

var autogenKeyString = BitConverter.ToString(autogenKeys).Replace("-", string.Empty);
var encryptionKeyString = BitConverter.ToString(decryptionKey).Replace("-", string.Empty);
var validationKeyString = BitConverter.ToString(validationKey).Replace("-", string.Empty);

using (var writer = new System.IO.StreamWriter("c:/somewhere/withwriteaccess/MachineKey.config")) {
    writer.Write(string.Format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<machineKey decryptionKey=\"{0}\" validationKey=\"{1}\" />", encryptionKeyString, validationKeyString));
}
%>

0

你实际上需要这个密钥吗? 还是只需要加密和解密数据?

System.Web.Security.FormsAuthentication (.NET 2.0) 具有公共的 Encrypt/Decrypt 方法。 这些方法使用 System.Web.Configuration.MachineKeySection 的 EncryptOrDecryptData、ByteArrayToHexString 和 HexStringToByteArray 来加密和解密数据。

EncryptOrDecryptData 可以处理从配置文件/AutoGenerate 加载/配置密钥数据的要求。

通过源代码下载或反编译,Encrypt 和 Decrypt 应该可以轻松转换为您所需的目的。


哇,那是很久以前的事了。是的,我确实需要特定的机器密钥。 - blowdart
@blowdart:你有找到访问密钥的方法吗?我也在寻找同样的东西。 - user807440

-2
请将以下配置信息添加到您的web.config文件中。确保用您自己的信息替换掉原有信息。
<system.web>
<machineKey validationKey="E4451576F51E0562D91A1748DF7AB3027FEF3C2CCAC46D756C833E1AF20C7BAEFFACF97C7081ADA4648918E0B56BF27D1699A6EB2D9B6967A562CAD14767F163" 
            decryptionKey="6159C46C9E288028ED26F5A65CED7317A83CB3485DE8C592" validation="HMACSHA256" decryption="AES" />
</system.web>

验证密钥和解密密钥应根据您的服务器和协议而有所不同。


这是一个不错的例子:http://arunendapally.com/post/implementation-of-single-sign-on-%28sso%29-in-asp.net-mvc - Amir Md Amiruzzaman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接