Тест на инструкции AES-NI от C#

Я хочу знать, есть ли способ проверить наличие AES-NI в ЦП хост-системы из C#.NET.

Позвольте мне сразу сказать, что этот вопрос не касается того, как использовать AES-NI из.NET. Получается просто используя AESCryptoServiceProvider будет использовать AES-NI, если он доступен. Этот результат основан на независимых тестах, которые я сделал, сравнивая выступления AESCryptoServiceProvider по сравнению с эталонами, представленными в TrueCrypt, который действительно поддерживает AES-NI. Результаты были удивительно похожи на обеих машинах с AES-NI и без него.

Причина, по которой я хочу проверить его, заключается в том, чтобы указать пользователю, что его компьютер поддерживает AES-NI. Это было бы актуально, так как это уменьшило бы количество инцидентов поддержки, связанных с такими вопросами, как "но у моего друга также есть Core i5, но он намного быстрее!" Если пользовательский интерфейс программы может указывать пользователю, что его система поддерживает или не поддерживает AES-NI, также можно было бы указать, что "более низкая производительность является нормальной, поскольку эта система не поддерживает AES-NI".

(Мы можем поблагодарить Intel за всю путаницу с различными степенями процессора!:-))

Есть ли способ обнаружить эту информацию, возможно, через WMI?

2 ответа

Похоже, что с SO такой же вопрос: встроенный код сборки для получения идентификатора процессора с отличным ответом.

Но этот ответ требует некоторых корректировок в соответствии с вашими потребностями.

Во-первых, как я понимаю, AES-NI может присутствовать только на 64-битных процессорах, верно? Тогда вы можете игнорировать весь 32-битный код в ответе выше.

Во-вторых, вам нужен регистр ECX или, вернее, его 25-й бит, поэтому вы должны немного изменить код:

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

Наконец, вам нужно сохранить регистр ECX в массиве:

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 */
};

Насколько я вижу, это все изменения.

Ответ от Марка выше фантастический и заставил меня работать хорошо, однако я заметил, что если приложение запускается в 32-битном режиме, регистр ecx не извлекается в коде x86, что не приводит к обнаружению AES-NI,

Я добавил одну строку и изменил другую, в основном применяя изменения, внесенные Марком в код x64, в код x86. Это позволяет вам видеть бит AES-NI в 32-битном режиме. Не уверен, поможет ли это кому-то, но я думал, что выложу это.

РЕДАКТИРОВАТЬ: В то время как я проводил некоторое тестирование, я заметил, что регистры, возвращаемые кодом x64, были неправильными. EDX возвращался со смещением 0x4, 0x8 и 0xC, кроме того, регистры ECX и EDX имели разные смещения с кодом x86, поэтому вам нужно чаще проверять IntPtr.Size, чтобы все работало в обеих средах. Для упрощения я помещаю регистр ECX в 0x4 и EDX в 0x8, и таким образом данные располагаются правильно.

Если кто-то попросит, я могу опубликовать весь класс, который является рабочим примером того, что я узнал из этого поста и других.

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);
Другие вопросы по тегам