Getting the current ASP.NET machine key
Asked Answered
C

9

24

I find myself wanting to get the ASP.NET machine key for the current application. This is, of course, easy if a machine key is specified in the configuration file, but if it's set to auto generate then there doesn't seem to be a public method anywhere to get it.

Basically I want at it so I can write an encrypted/MACed cookie for myself, just like the ASP.NET Forms Authentication provider does.

Does anyone have any pointers or ideas?

Contrarious answered 18/11, 2009 at 10:42 Comment(0)
S
19

Mr. Curious was curious about getting the machine key as well. The properties on the MachineKeySection are no good, as they get zeroed-out after initialization, which happens before you can read them with reflection.

After a bit of digging in the current 4.5 framework, turns out that the auto generated keys are stored in HttpApplication.s_autogenKeys byte array. The validation key is the first 64 bytes, followed by 24 bytes of the decryption key.

If you are not opting in into the new crypto stuff in 4.5 framework, that is, you didn't set <httpRuntime targetFramework="4.5"> in your web.config (which is the case if you have an app you created with a previous version of the framework), then you get to the keys like this:

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

The default for both keys is AutoGenerate,IsolateApps; the IsolateApps bit requires that you copy the first four bytes of the application path hash to the beginning of the key.

If you opted in into the cryptographic improvements in fx4.5, then you'll have to dig around the MachineKeyMasterKeyProvider to get the valid keys.

Getting the Keys without the HttpApplication

The HttpApplication gets its keys by calling into a native method in webengine4.dll from SetAutogenKeys(). We can call into the DLL ourselves as well. All we need to know is our application path.

Let's say that we want to get the auto generated keys for the root application, "/".

Using 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()));
    }
}

Getting the keys from MachineKeyMasterKeyProvider

The keys for the new fx4.5 stuff are accessible by instantiating the MachineKeyMasterKeyProvider with the internal constructor, and then passing in autogenKeys byte array obtained as in the code above. The provider has methods GetEncryptionKey and GetValidationKey to get to actual keys.

Snyder answered 21/3, 2014 at 12:49 Comment(8)
When you say app path, do you mean the IIS app path, e.g. "/" being the default web site? Or do you mean the absolute path to a folder containing the assembly on disk?Spermine
@thebringking, the IIS app path that you'd obtain with HttpRuntime.AppDomainAppVirtualPath.Snyder
thanks I was able to get closer. I am trying to accomplish this with my .net 4.5 project. I am able to get the bytes for the autogenKeys, and I took a look at MachineMasterKeyProvider class. How would you approach this in .net 4.5? I am trying to go down the road of generating a valid derived key in the same manner as the MachineMasterKeyProvider.Spermine
I should note that my goal is to be able to extract the auto-gen key or a valid derivative, and set it on the new server so existing tokens will validateSpermine
My approach was to copy relevant methods from .net framework source into a new project and then pare that down to a minimum needed encrypt/decrypt a cookie identical to the one I got with a network sniffer.Snyder
@Spermine I'd probably start by re-creating a pared-down version of AspNetCryptoServiceProvider as it seems to contain the calls to get the keys, and then keep copying methods from the framework source until I had a stand-alone solution. If you ever solve this, please post an answer here, maybe someone else will find it useful.Snyder
@thebringking, I've updated the answer with a hint to get to the keys from the MachineKeyMasterKeyProvider. Hope it helps.Snyder
Great answer! I used the code to extract the keys from my live site (running under www. as subdomain) and copied them to a machineKeys-element in the web.config on my beta-site (running under beta. as subdomain) . Now, logging in on the live site, also gets me logged in on my beta site. Thank you for sharing!Medlock
E
7

For .Net 4.5 here is the code

//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("-", "");
Extrusion answered 12/3, 2016 at 5:58 Comment(1)
This worked for me, not the accepted answer (use case: I needed to hardcode the EXISTING machinekey in web.config so users don't have to re-login into the app)Pontic
P
6

If you're using .NET 4, there's the MachineKey class. It doesn't give you raw access to the actual key, but it does provide methods for Encoding and Decoding the data using the same algorithms as the FormsAuthentication class, along with options for adding validation w/ an HMAC.

Psychochemical answered 4/10, 2011 at 18:33 Comment(0)
W
3

Thanks Mr. Curious,

based on your pointers I got this:

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");
}
Waterline answered 10/8, 2015 at 12:37 Comment(1)
For 4.5, this is the right answer. I haven't tried this code verbatim, but came up with a similar solution before I saw this answer. I also wanted to be able to set the keys without committing them to the web.config, full solution here: gist.github.com/cmcnab/d2bbed02eb429098ed3656a0729ee40aCappadocia
E
1

If the ASP.NET Forms Authentication provider can access it then have you tried looking at the provider source code? (I think this is the correct location, ScottGu's original blog post on the subject has had broken links since they updated MSDN)

Estis answered 18/11, 2009 at 11:17 Comment(3)
The Forms Auth provider can get at it because there are internal methods to allow it :)Contrarious
And I assume it's not possible to reproduce those methods because the source for those internal methods isn't available?Estis
They're available, kind of, but the actual backing store for the auto generated MAC key isn't, so even if used reflector, cut and pasted the methods, the actual key itself seems inaccessible. Which makes me wonder if I missed something!Contrarious
D
1

I came up with this as a combination of the answers above for post-4.5 .NET. Drop the code below in a file named mk.aspx, then browse to it to get the key. Be sure to delete it immediately after, because this is evil.

<%@ 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%>"
/>
Devilish answered 29/9, 2018 at 15:14 Comment(0)
R
0

Do you actually NEED the key? Or just to encrypt and decrypt the data?

System.Web.Security.FormsAuthentication (.NET 2.0) has public Encrypt/Decrypt methods. These use System.Web.Configuration.MachineKeySection EncryptOrDecryptData, ByteArrayToHexString and HexStringToByteArray to encrypt and decrypt the data.

EncryptOrDecryptData handles loading / configuring the key data from config files/AutoGenerate as required.

Encrypt And Decrypt should be available via the source code downloads or reflector and readily converted to your purpose.

Ramos answered 22/10, 2010 at 22:26 Comment(2)
Wow, that was a while back. And yes, I did need the machine key specifically.Contrarious
@blowdart: Did you ever find a way to access the key? I'm looking for the same thing.Viscosity
K
0

I had the same issue and needed to get the machinekey from a running web application (not using .NET 4.5 crypto features) that I could not make a code change to, so I created a simple .aspx file that extracts the key and dumps it to a file and then placed it in the application root and accessed it using a browser (without needing to touch the running application)

<%@ 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));
}
%>
Keratoid answered 29/11, 2017 at 10:9 Comment(0)
T
-2

Add the following config information to your web.config file. Make sure you replace information with your own information.

<system.web>
<machineKey validationKey="E4451576F51E0562D91A1748DF7AB3027FEF3C2CCAC46D756C833E1AF20C7BAEFFACF97C7081ADA4648918E0B56BF27D1699A6EB2D9B6967A562CAD14767F163" 
            decryptionKey="6159C46C9E288028ED26F5A65CED7317A83CB3485DE8C592" validation="HMACSHA256" decryption="AES" />
</system.web>

validationkey and decryptionkey, validation and decryption should vary based on your server and protocol.

Taproom answered 21/8, 2015 at 14:20 Comment(1)
here is a nice example: arunendapally.com/post/…Taproom

© 2022 - 2024 — McMap. All rights reserved.