1

我正在使用 Crypto++ 库实现AES256/GCM 加密和身份验证。我的代码是使用 Visual Studio 2008 作为 C++/MFC 项目编译的。这是一个较旧的项目,它使用了该库的早期版本,Cryptopp562.

我很好奇生成的编译代码是否会使用英特尔的AES-NI 指令?如果是这样,如果硬件(旧 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;
4

3 回答 3

3

如果您在不支持这些指令的 x86 硬件上运行包含 AES-NI 指令的代码,您应该会收到无效指令错误。除非代码做了一些聪明的事情(例如查看 CPUID 来决定是否运行 AES-NI 优化的代码,或者其他),否则这也可以用来检测是否实际使用了 AES-NI 指令。

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

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

于 2017-05-08T20:55:03.280 回答
2

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

Crypto++ 5.6.1 在 GCM 下增加了对 AES-NI 和无进位乘法的支持。当满足两个或三个条件时使用它。首先,您正在使用具有支持的库版本。从新闻(或自述文件)下的主页:

  • 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

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

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

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

第三,CPU同时支持AES和SSE4指令(下面解释SSE4指令的原因)。这些检查在运行时执行,感兴趣的函数被调用HasAES()cpu.h还有一个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;
}

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

关于第 (3) 项和运行时支持,我们最近不得不对其进行调整。似乎一些低端 Atom 处理器,如 D2500,有 SSE2、SSE3、SSSE3 和 AES-NI,但没有SSE4.1 或 SSE4.2。根据英特尔 ARK 的说法,它是处理器的可选配置。我们收到了一份关于 AES-NI 代码路径中的非法 SSE4 指令的错误报告,因此我们必须添加一项HasSSE4()检查。请参阅PR 172,在使用 SSE4.1 指令之前检查 SSE4 支持


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

没有什么。使用默认的 CXX 实现而不是硬件加速的 AES。

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


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

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

其次,有一个全局随机数生成器可供GlobalRNG(). GlobalRNG()在 OFB 模式下是 AES。当初始化程序为test.cpp翻译单元运行时,GlobalRNG()会创建 导致DetectX86Features()运行非常早(在控制进入之前main)。

使用 WinDbg 观察底层细节可能会更好。


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

于 2017-05-09T00:25:52.067 回答
2

只是为了完成我的问题,这是我的发现。

对于我上面的代码示例,将执行分叉到硬件支持指令的方法AES-NI,与 Crypto++ 库中软件实现的指令相比,位于. 它是这样开始的:Rijndael::Enc::AdvancedProcessBlocksrijndael.cpp

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如果您正在构建 Crypto++ 库,那么将定义预处理器变量(Visual Studio 2008 with SP1请注意,这SP1很重要。)这种依赖关系对于能够使用AES-NI内部函数(例如_mm_aesenc_si128_mm_aesenclast_si128)来生成英特尔的AES-NI机器代码指令是必要的。

所以在开头添加断点

在此处输入图像描述

将让您直接从 Visual Studio 调试它。不需要外部调试器。

如果您随后进入该AESNI_AdvancedProcessBlocks方法,实际的 AES 加密将在其中一种AESNI_Enc_*方法中进行处理。以下是构建中配置的实际指令aesencaesenclast机器指令的样子:x86Release

在此处输入图像描述

因此,要回答我最初的问题,为了使我上面帖子中的代码示例能够利用英特尔的AES-NI指令,需要构建代码示例和 Crypto++ 库,至少Visual Studio 2008 with SP1. (即使代码运行的 CPU 支持指令Visual Studio 2008,仅使用 或更早版本构建它也不会完成这项工作。)之后,似乎不需要其他步骤。该库将自动检测指令(函数)的存在,并在可用时使用它们。否则它将默认为软件实现。AES-NIAES-NIHasAESNI()

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

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

这里有两个结果:

在此处输入图像描述

在此处输入图像描述

这显然不是一个包罗万象的测试。"Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz"我用CPU在我的桌面上运行它。

但好消息是AES-GCM加密似乎非常快,即使没有硬件 AES 支持。

于 2017-05-12T07:29:33.767 回答