使用AES / GCM加密的已编译Crypto ++库代码是否使用了英特尔的AES-NI指令?

时间:2017-05-08 20:51:56

标签: c++ aes crypto++ aes-gcm aes-ni

我使用Crypto ++库实现AES256/GCM encryption和身份验证。我的代码使用Visual Studio 2008编译为C ++ / MFC项目。这是一个较旧的项目,它使用以前版本的库Cryptopp562

我很好奇,如果生成的编译代码将使用英特尔AES-NI instructions?如果是这样,如果硬件(较旧的CPU)不支持它会发生什么?

编辑: 以下是我测试的代码示例:

int nIV_Length = 12;
int nAES_KeyLength = 32;
BYTE* iv = new BYTE[nIV_Length];
BYTE* key = new BYTE[nAES_KeyLength];

int nLnPlainText = 128;
BYTE* pDataPlainText = new BYTE[nLnPlainText];

CryptoPP::AutoSeededRandomPool rng;
rng.GenerateBlock(iv, nIV_Length);

CryptoPP::GCM<CryptoPP::AES>::Encryption enc;
enc.SetKeyWithIV(key, nAES_KeyLength, iv, nIV_Length);

BYTE* pDataOut_AES_GCM = new BYTE[nLnPlainText];
memset(pDataOut_AES_GCM, 0, nLnPlainText);

BYTE mac[16] = {0};
enc.EncryptAndAuthenticate(pDataOut_AES_GCM, mac, sizeof(mac), iv, nIV_Length, NULL, 0, pDataPlainText, nLnPlainText);

delete[] pDataPlainText;
delete[] pDataOut_AES_GCM;
delete[] key;
delete[] iv;

3 个答案:

答案 0 :(得分:2)

如果在不支持这些指令的x86硬件上运行包含AES-NI指令的代码,则应该得到无效的指令错误。除非代码执行智能操作(例如查看CPUID以决定是否运行AES-NI优化代码或其他内容),否则这也可用于检测是否实际使用了AES-NI指令。

否则,您始终可以使用调试器,并在AES-NI指令处设置断点,以查看您的进程是否曾使用过该部分代码。

根据Crypto++ release notes AES-NI支持版本5.6.1中添加了。查看5.6.5版Crypto ++的源代码,如果在编译时启用了AES-NI支持,那么它使用运行时检查(HasAESNI()函数,可能利用CPUID)来决定是否使用这些内在函数。有关详细信息,请参阅其源代码中的rijndael.cpp(以及cpu.cpp中的CPUID代码)。

答案 1 :(得分:1)

  

我很好奇所得到的编译代码是否会使用英特尔的AES-NI指令?

Crypto ++ 5.6.1在GCM下增加了对AES-NI和Carryless Multiplies的支持。当满足两个或三个条件时使用它。首先,您使用的是支持的库版本。来自 新闻 (或自述文件)下的主页:

  
      
  • 2010年8月9日 - 版本5.6.1已发布

         
        
    • 在AES和GMAC / GCM中添加了对AES-NI和CLMUL指令集的支持
    •   
  •   

其次,编译器,汇编器和链接器必须支持这些指令。对于Crypto ++,这意味着您至少使用MSVC 2008 SP1,GCC 4.3和Binutils 2.19。对于MSVC,如果你看config.h,它的守护如下(__AES__也适用于GCC和朋友):

#if ... (_MSC_FULL_VER >= 150030729) ...
    #define CRYPTOPP_BOOL_AESNI_INTRINSICS_AVAILABLE 1
#else
    #define CRYPTOPP_BOOL_AESNI_INTRINSICS_AVAILABLE 0
#endif

您可以在_MSC_FULL_VER查找Visual Studio version个号码。具有讽刺意味的是,即使服务包很重要,我也从未在MSDN上看到类似的页面。你必须去一个中国网站。例如,已检查的迭代器出现在VS2005 SP1(IIRC)中。

对于Linux和GCC兼容,GNUmakefile检查编译器和汇编器的版本。如果它们太旧,则makefile会将CRYPTOPP_DISABLE_AESNI添加到命令行以禁用支持,即使已定义__AES__

CRYPTOPP_DISABLE_AESNI会更频繁地显示出来。例如,如果您下载OpenBSD 6.0(当前版本),那么 CRYPTOPP_DISABLE_AESNI将会出现,因为他们的汇编程序太旧了。他们大多停留在他们工具的GPL-2版本之前(显然他们不同意许可证更改)。

第三,CPU支持AES和SSE4指令(下面解释了SSE4指令的原因)。这些检查在运行时执行,感兴趣的函数从cpu.h调用HasAES()(还有一个HasSSE4()):

//! \brief Determines AES-NI availability
//! \returns true if AES-NI is determined to be available, false otherwise
//! \details HasAESNI() is a runtime check performed using CPUID
inline bool HasAESNI()
{
    if (!g_x86DetectionDone)
        DetectX86Features();
    return g_hasAESNI;
}

Item(3)的警告是需要在Item(2)的支持下编译的库。如果Item(2)不包含编译时支持,则Item(3)不能提供运行时支持。

关于Item(3)和运行时支持,我们最近不得不调整它。似乎一些低端Atom处理器,如D2500,有SSE2,SSE3,SSSE3和AES-NI,但 SSE4.1或SSE4.2 。据英特尔ARK称,它是处理器的可选配置。我们收到了一条关于AES-NI代码路径中非法SSE4指令的错误报告,因此我们必须添加HasSSE4()项检查。请参阅PR 172, Check for SSE4 support before using SSE4.1 instruction

  

如果是这样,如果硬件(较旧的CPU)不支持它会发生什么?

无。使用默认的CXX实现而不是硬件加速的AES。

您可能有兴趣知道我们还有其他AES硬件加速,包括ARMv8 Crypto和VIA Padlock。我们还提供其他硬件加速,如CRC32,Carryless-Multiplies和SHA。它们都以相同的方式运行 - 编译时支持转换为运行时支持。

  

(评论):我只是在cpu.cpp中的DetectX86Features方法上设置一个断点......它从未触发...

由于两个原因,这可能很棘手。首先,可以在发布版本中内联调用,因此代码的形状与您期望的形状略有不同。

其次,GlobalRNG()访问了一个全局随机数生成器。 OFB模式下的GlobalRNG()是AES。为test.cpp翻译单元运行初始值设定项时,会创建GlobalRNG(),这会导致DetectX86Features()很早就开始运行(在控件进入main之前)。

使用WinDbg观察低级细节可能会更好。

还值得一提的是AES / GCM可以通过将AES与GCM交错来加速。我相信这个想法是并行执行4轮AES密钥计算和1个CLMUL。 Crypto ++没有利用它,但OpenSSL抓住了机会。我不知道Botan或mbedTLS做了什么。

答案 2 :(得分:0)

刚刚完成我的问题,这是我的发现。

分支执行硬件支持的AES-NI指令与我code sample above的Crypto ++库中软件实现的指令的方法Rijndael::Enc::AdvancedProcessBlocks位于{ {1}}。它是这样开始的:

rijndael.cpp

如果您构建的Crypto ++库至少为size_t Rijndael::Enc::AdvancedProcessBlocks(const byte *inBlocks, const byte *xorBlocks, byte *outBlocks, size_t length, word32 flags) const { #if CRYPTOPP_BOOL_AESNI_INTRINSICS_AVAILABLE if (HasAESNI()) return AESNI_AdvancedProcessBlocks(AESNI_Enc_Block, AESNI_Enc_4_Blocks, (MAYBE_CONST __m128i *)(const void *)m_key.begin(), m_rounds, inBlocks, xorBlocks, outBlocks, length, flags); #endif ,则将定义CRYPTOPP_BOOL_AESNI_INTRINSICS_AVAILABLE预处理器变量(注意Visual Studio 2008 with SP1很重要。)这种依赖关系是必要的。使用SP1内在函数(例如_mm_aesenc_si128_mm_aesenclast_si128)生成英特尔的AES-NI机器代码指令。

因此在

的开头添加断点

enter image description here

将允许您直接从Visual Studio进行调试。无需外部调试器。

如果您再进入AES-NI方法,将使用其中一种AESNI_AdvancedProcessBlocks方法处理实际的AES加密。以下是AESNI_Enc_*版本中aesenclast配置的实际aesencx86机器说明的外观:

enter image description here

因此,为了回答我原来的问题,对于上面帖子中的代码示例,为了能够利用英特尔的Release指令,我们需要构建至少AES-NI的代码示例和Crypto ++库。 (只需使用Visual Studio 2008 with SP1或早期版本构建它,不会执行此工作,即使运行代码的CPU支持Visual Studio 2008说明。)之后,似乎没有必要采取其他步骤。该库将自动检测AES-NI指令的存在(AES-NI函数),并在可用时使用它们。否则它将默认为软件实现。

最后,仅仅出于好奇,我决定看看硬件与软件AES-GCM加密在速度上会产生多大差异。我使用了以下代码片段(来自我上面的代码示例):

HasAESNI()

以下是两个结果:

enter image description here

enter image description here

这显然不是一个包罗万象的测试。我使用int nCntTest = 100000; DWORD dwmsIniTicks = ::GetTickCount(); for(int i = 0; i < nCntTest; i++) { enc.EncryptAndAuthenticate(pDataOut_AES_GCM, mac, sizeof(mac), iv, nIV_Length, NULL, 0, pDataPlainText, nLnPlainText); } DWORD dwmsElapsed = ::GetTickCount() - dwmsIniTicks; bool bHaveHwAES_Support = false; #if CRYPTOPP_BOOL_AESNI_INTRINSICS_AVAILABLE bHaveHwAES_Support = CryptoPP::HasAESNI(); #endif _tprintf(L"\nTimed %d AES256-GCM encryptions %s hardware encryption of %d bytes: %u ms\n", nCntTest, bHaveHwAES_Support ? L"with" : L"without", nLnRealPlainText, dwmsElapsed); CPU在桌面上运行它。

但好消息是即使没有硬件AES支持,"Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz"加密似乎也非常快。