测试来自 C# 的 AES-NI 指令

Posted

技术标签:

【中文标题】测试来自 C# 的 AES-NI 指令【英文标题】:Test for AES-NI instructions from C# 【发布时间】:2015-01-13 04:07:29 【问题描述】:

我想知道是否有一种方法可以通过 C#.NET 测试主机系统 CPU 中是否存在 AES-NI。

让我先说一下,这个问题不是询问如何使用来自 .NET 的 AES-NI。事实证明,简单地使用AESCryptoServiceProvider 将使用 AES-NI(如果可用)。该结果基于我将AESCryptoServiceProvider 的性能与 TrueCrypt 中提供的基准进行比较的独立基准,TrueCrypt 确实支持 AES-NI。在使用和不使用 AES-NI 的两台机器上,结果惊人地相似。

我希望能够对其进行测试的原因是能够向用户表明他们的计算机支持 AES-NI。这将是相关的,因为它可以减少涉及诸如“但我的朋友也有 Core i5 但他的速度要快得多!”之类的问题的支持事件。如果程序的用户界面可以向用户表明他们的系统支持或不支持 AES-NI,那么它也可能表明“性能较慢是正常的,因为该系统不支持 AES-NI。”

(我们要感谢英特尔对不同处理器步进的所有困惑!:-))

有没有办法检测这些信息,也许是通过 WMI?

【问题讨论】:

不知道答案,但我首先要看的也是一些 OS WMI 调用。 与论坛网站不同,我们不使用“谢谢”、“感谢任何帮助”或Stack Overflow 上的签名。请参阅“Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?. 从看起来你需要调用汇编指令 CPUID 并将 EAX 设置为 1,这将在 EDX 和 ECX 中输出一些用于处理器功能的位标志。您关心 ECX 中的第 25 位设置为 1,这意味着它支持 AES-NI。唯一的问题是我无法找到通过托管代码或 WMI 从 CPUID 获取 ECX 值的方法。我得到的最接近的是 WMI 调用 Win32_ProcessorProcessorID 属性,它是 CPUID 调用后 EAX 和 EDX 寄存器的位掩码,但它不包括您关心的 ECX。 【参考方案1】:

似乎在 SO:Inline Assembly Code to Get CPU ID 上有类似的问题,答案很好。

但是这个答案需要根据你的需要进行一些调整。

首先,据我了解,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 */
;

据我所知,这就是所有变化。

【讨论】:

如何在 windows 上的 c++ ( GCC ) 中做同样的事情? @Canadian_Republican,GCC 支持 CPUID:***.com/questions/14266772/…【参考方案2】:

上面 Mark 的回答非常棒,对我来说效果很好,但是我确实注意到,如果应用程序在 32 位模式下运行,则不会在 x86 代码中提取 ecx 寄存器,从而导致无法检测到AES-NI。

我添加了一行并更改了另一行,基本上是将 Mark 对 x64 代码所做的更改应用到 x86 代码。这也允许您从 32 位模式查看 AES-NI 位。不确定它是否会帮助某人,但我想我会发布它。

编辑:当我进行一些测试时,我注意到 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);

【讨论】:

以上是关于测试来自 C# 的 AES-NI 指令的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 __cpuid 检查 AES-NI 支持?

测试容器:忽略来自 Dockerfile 的父“EXPOSE”指令

Patch OpenSSL使其支持CHACH20_POLY1305加密算法

C# 预处理器指令

c# 编译器指令在持续交付管道中使用

C# 预编译指令 - 使用变量来告诉编译哪些代码