6

I want to know if there is a way to test for the presence of AES-NI in the host system's CPU from C#.NET.

Let me say up front that this question is not asking about how to use AES-NI from .NET. It turns out simply using AESCryptoServiceProvider will use AES-NI if it is available. This result is based on independent benchmarks I did comparing the performances of AESCryptoServiceProvider against the benchmarks provided in TrueCrypt, which does indeed support AES-NI. The results were surprisingly similar on both machines with and without AES-NI.

The reason I want to be able to test for it is to be able to indicate to the user that their computer supports AES-NI. This would be relevant since it would reduce support incidents involving questions like "but my friend has a Core i5 also but his is a lot faster!" If the program's user interface could indicate to the user that their system does or does not support AES-NI, it would also be possible to indicate that "slower performance is normal since this system does not support AES-NI."

(We can thank Intel for all of the confusion with different processor steppings! :-) )

Is there a way to detect this information, perhaps through WMI?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
fdmillion
  • 4,823
  • 7
  • 45
  • 82
  • Don't know the answer, but the first place I would look would be some OS WMI call too. – Scott Chamberlain Jan 13 '15 at 04:10
  • Unlike forum sites, we don't use "Thanks", or "Any help appreciated", or signatures on [so]. See "[Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?](http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be-removed-from-posts). – John Saunders Jan 13 '15 at 04:18
  • From what it looks like you need to call the assembly instruction [CPUID](http://en.wikipedia.org/wiki/CPUID) with EAX set to 1, that will output some bitflags in EDX and ECX for processor features. You care about bit 25 in ECX being set to 1, that means it supports AES-NI. The only problem is I have not been able to find a way to get the ECX value from CPUID via managed code or WMI. The closest I got was the `ProcessorID` property of the WMI call `Win32_Processor` which is a bitmask of the EAX and EDX registers after the CPUID call but it does not include the ECX ones you care about. – Scott Chamberlain Jan 13 '15 at 04:48

2 Answers2

2

It's seems that there is the similar question on SO: Inline Assembly Code to Get CPU ID with the great answer.

But this answer requires some adjustments to suit your need.

First, as I understand, AES-NI can be presence at 64-bit processors only, right? Then you could ignore all 32-bit code in the answer above.

Second, you need ECX register or rather its 25th bit, so you must change code a bit:

private static bool IsAESNIPresent()
{
    byte[] sn = new byte[16]; // !!! Here were 8 bytes

    if (!ExecuteCode(ref sn))
        return false;

    var ecx = BitConverter.ToUInt32(sn, 8);
    return (ecx & (1 << 25)) != 0;
}

Finally, you need store ECX register in array:

byte[] code_x64 = new byte[] {
    0x53,                                     /* push rbx */
    0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, /* mov rax, 0x1 */
    0x0f, 0xa2,                               /* cpuid */
    0x41, 0x89, 0x00,                         /* mov [r8], eax */
    0x41, 0x89, 0x50, 0x04,                   /* mov [r8+0x4], ebx !!! changed */
    0x41, 0x89, 0x50, 0x08,                   /* mov [r8+0x8], ecx !!! added */
    0x41, 0x89, 0x50, 0x0C,                   /* mov [r8+0xC], edx !!! added*/
    0x5b,                                     /* pop rbx */
    0xc3,                                     /* ret */
};

As far as I can see, that's all changes.

Community
  • 1
  • 1
Mark Shevchenko
  • 7,937
  • 1
  • 25
  • 29
1

The answer from Mark above is fantastic and got things working well for me, however I did notice that if the application is run in 32 bit mode that the ecx register wasn't pulled in the x86 code thus leading to no detection of AES-NI.

I added one line and changed another, basically applying the changes Mark made to the x64 code to the x86 code. This allows you to see the AES-NI bit from 32 bit mode as well. Not sure if it will help someone, but I thought I'd post it.

EDIT: While I was doing some testing, I noticed that the registers returned by the x64 code were incorrect. EDX was being returned at offset 0x4, 0x8, and 0xC, additionally the ECX and EDX registers were at different offsets with x86 code so you needed to check IntPtr.Size more often to keep things working in both environments. To simplify things I place the ECX register at 0x4 and EDX at 0x8 and that way the data is arranged correctly.

If someone requests I can post up the entire class that is a working example of what I've learned from this post and others.

public static bool ExecuteCode(ref byte[] result) {
    byte[] code_x86 = new byte[] {
        0x55,                      /* push ebp */
        0x89, 0xE5,                /* mov  ebp, esp */
        0x57,                      /* push edi */
        0x8b, 0x7D, 0x10,          /* mov  edi, [ebp+0x10] */
        0x6A, 0x01,                /* push 0x1 */
        0x58,                      /* pop  eax */
        0x53,                      /* push ebx */
        0x0F, 0xA2,                /* cpuid    */
        0x89, 0x07,                /* mov  [edi], eax */
        0x89, 0x4F, 0x04,          /* mov  [edi+0x4], ecx Changed */
        0x89, 0x57, 0x08,          /* mov  [edi+0x8], edx Changed */
        0x5B,                      /* pop  ebx */
        0x5F,                      /* pop  edi */
        0x89, 0xEC,                /* mov  esp, ebp */
        0x5D,                      /* pop  ebp */
        0xC2, 0x10, 0x00,          /* ret  0x10 */
    };
    byte[] code_x64 = new byte[] {
        0x53,                                     /* push rbx */
        0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, /* mov rax, 0x1 */
        0x0f, 0xA2,                               /* cpuid */
        0x41, 0x89, 0x00,                         /* mov [r8], eax */
        0x41, 0x89, 0x48, 0x04,                   /* mov [r8+0x4], ecx Changed */
        0x41, 0x89, 0x50, 0x08,                   /* mov [r8+0x8], edx Changed*/
        0x5B,                                     /* pop rbx */
        0xC3,                                     /* ret */
    };

    int num;
    byte[] code = (IntPtr.Size == 4) ? code_x86 : code_x64;
    IntPtr ptr = new IntPtr(code.Length);

    if (!VirtualProtect(code, ptr, PAGE_EXECUTE_READWRITE, out num))
        Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

    ptr = new IntPtr(result.Length);

    return (ExecuteNativeCode(code, IntPtr.Zero, 0, result, ptr) != IntPtr.Zero);