views:

2788

answers:

6

I'm using the .NET 3.0 class System.Security.Cryptography.MACTripleDES class to generate a MAC value. Unfortunately, I am working with a hardware device that uses "1111111111111111" (as hex) as a single-length DES key. The System.Security.Cryptography library does some sanity checking on the key and returns a Exception if you try to use a cryptographically weak key.

For example:

byte[] key = new byte[24];
for (int i = 0; i < key.Length; i++)
  key[i] = 0x11;

byte[] data = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte[] computedMac = null;
using (MACTripleDES mac = new MACTripleDES(key))
{
  computedMac = mac.ComputeHash(data);
}

throws an exception

System.Security.Cryptography.CryptographicException : Specified key is a known weak key for 'TripleDES' and cannot be used.

I know this is not a secure key. In production, the device will be flashed with a new, secure key. In the mean time, is there any way to inhibit this Exception from being thrown? Perhaps an app.config or registry setting?

Edit: The key would actually be 101010... due to the algorithm forcing odd parity. I'm not sure if this is universal to the DES algorithm or just a requirement in the payment processing work I do.

Edit 2: Daniel's answer below has some very good information about hacking .NET. Unfortunately, I wasn't able to solve my problem using this technique, but there is still some interesting reading there.

+1  A: 

Unfortunately, the behaviour can't be overridden.

Dave Cluderay
+6  A: 

I wouldn't really recommend it, but you should be able to modify the IL-code that checks for weak keys using Reflector and the Add-in ReflexIL

edit:

Sorry, it took a while for me to load all of it up in my Virtual Machine (running Ubuntu) and didn't want to mess with Mono.

  • Install the ReflexIL Add-in: View -> Add-ins -> Add
  • Open ReflexIL: Tools -> ReflexIL v0.9
  • Find the IsWeakKey() function. (You can use Search: F3)
  • Two functions will come up, doubleclick the one found in System.Security.Cryptography.TripleDES
  • ReflexIL should have come up too. In the Instructions tab, scroll all the way down to line 29 (offset 63).
  • Change ldc.i4.1 to ldc.i4.0, this means the function will always return false.

In your assemblies pane (left one), you can now scroll up and click on "Common Language Runtime Library", the ReflexIL pane will give you an option to save it.

Important notes:

  • BACK UP your original assembly first! (mscorlib.dll)
  • mscorlib.dll is a signed assembly and you will need the .NET SDK (sn.exe tool) for ReflexIL to make it skip verification. I just checked this myself, you should already have this with Visual C# installed. Just click "Register it for verification skipping (on this computer)" when asked to.
  • I don't think I have to tell you to only use this on your development machine :)

Good luck! If you need additional instructions, please feel free to use the commentbox.

edit2:

I'm confused!

I completely removed the IsWeakKey check from the set_Key function in the mscorlib assembly. I am absolutely certain that I modified the correct function, and that I did it correctly. Reflector's disassembler does no longer show the check. The funny thing is however, that Visual C# still throws the same exception.

This leads me to believe that mscorlib must somehow still be cached somewhere. However, renaming mscorlib.dll to mscorlib.dll_ leads MSVC# to crash, so it must still be dependent on the original dll.

This is quite interesting stuff, but I think I've reached the point where I have no clue what is going on, it just doesn't make any sense! See attached image. :(

edit3:

I notice in Olly, that unlike assemblies such as mscoree, mscorsec and mscorwks; mscorlib.dll isn't actually located in: c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\

But instead, in what appears to be a non-existent location: C:\WINDOWS\assembly\NativeImages_v2.0.50727_32\mscorlib\6d667f19d687361886990f3ca0f49816\mscorlib.ni.dll

I think I am missing something here :) Will investigate this some more.

edit4:

Even after having patched out EVERYTHING in IsWeakKey, and played around with both removing and generating new native images (x.ni.dll) of mscorlib.dll using "ngen.exe", I am getting the same exception. I must be noted that even after uninstalling the native mscorlib images, it is still using mscorlib.ni.dll... Meh.

I give up. I hope someone will be able to answer what the hell is going on because I sure don't know. :)

Daniel
Pretty interesting solution! I patched my mscorlib.dll, but there's no change in the behavior of the function. Is this dll cached somewhere? All I am changing is ....\Microsoft.NET\Framework\v2.0.50737\mscorlib.dll.
David Chappelle
I just tested the code, you're right. I will investigate and report back :)
Daniel
This is weird to say the least. I have now modified the actual IsWeakKey check on set_Key(), and it is doing the same thing. It must be cached somewhere. I shall investigate some more :)
Daniel
I am not familiar with this portion of .NET internals. I thought C:\WINDOWS\assembly was where assemblies in the GAC lived. Using Process Explorer, I see that my app has an open handle to mscorlib.ni.dll. There is an old blog entry at http://blogs.msdn.com/junfeng/archive/2004/11/11/256122.aspx..
David Chappelle
Yeah. It is using a native image of mscorlib.dll which can be generated using the ngen.exe tool (ngen.exe install mscorlib.dll). First time you run it it'll tell you the native image is already up-to-date. (which it isn't). Even when you remove the native image it will load mscorlib.ni.dll.
Daniel
And uninstalling, and then trying to install mscorlib.dll (which was modified by reflector) ends up giving me the same results.. An exception is thrown even though that check has been completely taken out.
Daniel
I am fairly sure that the underlying CryptoAPI implementation enforces the weak-key restriction, too. So you would have to patch advapi32.dll, too (which I would not recommend).
Rasmus Faber
@Rasmus, according to the debugger, exception is thrown from System.Security.Cryptography.TripleDES.set_Key... I am not familiar with .NET internals myself (hell, I've never touched C#/VB/ASP), so excuse me for asking why considering the above, the exception is still thrown from that location?
Daniel
If the exception is thrown from set_Key, then you are probably right that the problem is still caused by the .NET IsWeakKey()-check. My comment was more to the point that even if you got the code to use your patched assembly, the code would still fail when it reached the CryptoAPI-call.
Rasmus Faber
Ah yes. I wouldn't really know about that to be honest. :)I guess this was still somewhat interesting to do, unfortunately couldn't completely answer David's question.
Daniel
I appreciate the effort you put into this. I think I will just use one of the open source, unmanaged crypto libraries and interop into it. The Reflector tool is pretty interesting (and scary? Maybe I should start running the obfuscator after all...)
David Chappelle
@David, I must again note that I am not very familiar with the .NET framework, but 'dotfuscator' (shipped by default in MSVS I believe?) only obfuscates class-names, function-names, variable-names, etcetera. That said, even with 3rd party obfuscators its a matter of time before your code is reversed
Daniel
I looked at it before posting that it wasn't possible (I guess I should have stated "without modifying system assemblies"). The exception is thrown in the TripleDESCryptoServiceProvider class immediately before calling its private _NewEncryptor (or something like that) method - hence set_Key failing
Dave Cluderay
A: 

I'm not a security expert but wouldn't XORing your key with another value be enough to satisfy the sanity check? You could do this for your debug version (with proper IFDEF) so you can do proper checking and remove it for your release or production version where the key would be strong enough.

Robert Kozak
That would indeed satisfy the sanity check, but the hardware device he is working with would still be using the original key.
Daniel
Yeah. I just thought of that. I was only thinking about it from the code point of view and didn't even consider the hardware. My Bad :)
Robert Kozak
+1  A: 

Instead of using MACTripleDES with the DES key repeated to fake a single DES CBC-MAC, you could just implement CBC-MAC yourself on top of DESCryptoServiceProvider.

<1111111111111111> is not a weak DES key.

This will calculate a DES CBC-MAC:

public static byte[] CalcDesMac(byte[] key, byte[] data){
     DESCryptoServiceProvider des = new DESCryptoServiceProvider();
     des.Key = key;
     des.IV = new byte[8];
     des.Padding = PaddingMode.Zeros;
     MemoryStream ms = new MemoryStream();
     using(CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write)){
       cs.Write(data, 0, data.Length);
     }
     byte[] encryption = ms.ToArray();
     byte[] mac = new byte[8];
     Array.Copy(encryption, encryption.Length-8, mac, 0, 8);
     PrintByteArray(encryption);
     return mac;
    }
Rasmus Faber
This works just fine. I think I was just focused on the MACTripleDES class, which if I'd thought it through, is really used for Triple DES only and not single DES. I wish it hadn't taken me a year for me to read your response! I'd ended up implementing CBC-MAC by hand.
David Chappelle
+1  A: 

I found out what you need to do. Fortunately there is a method that available that creates the ICryptoTranforms that doesn't check for weak keys. You also need to watch out for the base class as it also does sanity checks. Via reflection simply call out the _NewEncryptor method (you need to do a little more reflection, but that's the idea).

Luckily the MACTripleDES has a field of type TripleDES, so derive from MACTripleDES and replace it via reflection in the constructors. I have done all the work for you.

I can't verify that the correct MAC is generated, but no exceptions are thrown. Furthermore, you might want to doc comment the code and do exception handling (reflection failures - e.g. if the fields/methods are not there) - but this is SO; so I didn't bother.

using System;
using System.Reflection;
using System.Security.Cryptography;
using System.IO;

namespace DesHack
{
    class Program
    {
        static void Main(string[] args)
        {
            byte[] key = new byte[24];
            for (int i = 0; i < key.Length; i++)
                key[i] = 0x11;

            byte[] data = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
            byte[] computedMac = null;
            using (MACTripleDES mac = new MACTripleDESHack(key))
            {
                computedMac = mac.ComputeHash(data);
            }
        }
    }

    class MACTripleDESHack : MACTripleDES
    {
        TripleDES _desHack = new DesHack();

        static FieldInfo _cspField = typeof(MACTripleDES).GetField("des", BindingFlags.Instance | BindingFlags.NonPublic);

        public MACTripleDESHack()
            : base()
        {
            RewireDes();
        }

        public MACTripleDESHack(byte[] rgbKey)
            : base(rgbKey)
        {
            RewireDes();
        }

        private void RewireDes()
        {
            _cspField.SetValue(this, _desHack);
        }

    }

    class DesHack : TripleDES
    {
        TripleDESCryptoServiceProvider _backing = new TripleDESCryptoServiceProvider();

        static MethodInfo _newEncryptor;
        static object _encrypt;
        static object _decrypt;

        public override int BlockSize
        {
            get
            {
                return _backing.BlockSize;
            }
            set
            {
                _backing.BlockSize = value;
            }
        }

        public override int FeedbackSize
        {
            get
            {
                return _backing.FeedbackSize;
            }
            set
            {
                _backing.FeedbackSize = value;
            }
        }

        // For these two we ALSO need to avoid
        // the base class - it also checks
        // for weak keys.
        private byte[] _iv;
        public override byte[] IV
        {
            get
            {
                return _iv;
            }
            set
            {
                _iv = value;
            }
        }

        private byte[] _key;
        public override byte[] Key
        {
            get
            {
                return _key;
            }
            set
            {
                _key = value;
            }
        }

        public override int KeySize
        {
            get
            {
                return _backing.KeySize;
            }
            set
            {
                _backing.KeySize = value;
            }
        }

        public override KeySizes[] LegalBlockSizes
        {
            get
            {
                return _backing.LegalBlockSizes;
            }
        }

        public override KeySizes[] LegalKeySizes
        {
            get
            {
                return _backing.LegalKeySizes;
            }
        }

        public override CipherMode Mode
        {
            get
            {
                return _backing.Mode;
            }
            set
            {
                _backing.Mode = value;
            }
        }

        public override PaddingMode Padding
        {
            get
            {
                return _backing.Padding;
            }
            set
            {
                _backing.Padding = value;
            }
        }


        static DesHack()
        {
            _encrypt = typeof(object).Assembly.GetType("System.Security.Cryptography.CryptoAPITransformMode").GetField("Encrypt").GetValue(null);
            _decrypt = typeof(object).Assembly.GetType("System.Security.Cryptography.CryptoAPITransformMode").GetField("Decrypt").GetValue(null);
            _newEncryptor = typeof(TripleDESCryptoServiceProvider).GetMethod("_NewEncryptor", BindingFlags.NonPublic | BindingFlags.Instance);
        }

        public DesHack()
        {            
        }

        public override ICryptoTransform CreateDecryptor()
        {
            return CreateDecryptor(_key, _iv);
        }

        public override ICryptoTransform CreateEncryptor()
        {
            return CreateEncryptor(_key, _iv);
        }

        public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV)
        {
            // return this._NewEncryptor(rgbKey, base.ModeValue, rgbIV, base.FeedbackSizeValue, CryptoAPITransformMode.Decrypt);
            return (ICryptoTransform) _newEncryptor.Invoke(_backing,
                new object[] { rgbKey, ModeValue, rgbIV, FeedbackSizeValue, _decrypt });
        }

        public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV)
        {
            // return this._NewEncryptor(rgbKey, base.ModeValue, rgbIV, base.FeedbackSizeValue, CryptoAPITransformMode.Encrypt);
            return (ICryptoTransform) _newEncryptor.Invoke(_backing,
                new object[] { rgbKey, ModeValue, rgbIV, FeedbackSizeValue, _encrypt });
        }

        public override void GenerateIV()
        {
            _backing.GenerateIV();
        }

        public override void GenerateKey()
        {
            _backing.GenerateKey();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
                ((IDisposable) _backing).Dispose();
            base.Dispose(disposing);
        }
    }
}
Jonathan C Dickinson
A: 

There is a great suggestion using reflection in the MSDN forums