2

I'm implementing AES256/GCM encryption and authentication using Crypto++ library. My code is compiled using Visual Studio 2008 as a C++/MFC project. This is a somewhat older project that uses a previous version of the library, Cryptopp562.

I'm curious if the resulting compiled code will use Intel's AES-NI instructions? And if so, what happens if the hardware (older CPU) does not support it?

EDIT: Here's an example of code that I'm testing it with:

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;
c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • *"This is a somewhat older project that uses a previous version of the library, Crypto++ 5.6.2..."* - You might consider updating to Crypto++ 5.6.5 or master. We cleared ***a lot*** of undefined behavior and several hundred corner-case bugs in 5.6.3 through 5.6.5. Not to mention all the new algorithms you will get, from SipHash and BLAKE2 to Keccak and SHA-3. The only downside I am aware is we broke ABI compatibility. You have to recompile library and apps. – jww May 10 '17 at 20:32
  • Regarding the Crypto++ 5.6.5 or Master update, I keep [`vs2005.zip`](https://github.com/weidai11/cryptopp/blob/master/vs2005.zip) mostly up-to-date, so things should still "just work" for you. The last time it was changed was March 2017 when we back ported NIST DRBG's, Poly1305 and SipHash into the VS2005 project files. As of this writing, its missing Kalyna and Threefish. I'll get to that update in a couple of weeks (or you can manually add them). – jww May 10 '17 at 20:38
  • @jww: We chose 5.6.2 a while back since any later version of Crypto++ library (at the time) had memory leaks. – c00000fd May 11 '17 at 03:40
  • @c0000fd - If you are latched at Crypto++ 5.6.2, then you should probably pick up the changes marked as security-bugs. Also see [is:issue label:security-bug](https://github.com/weidai11/cryptopp/issues?utf8=%E2%9C%93&q=is%3Aissue%20label%3A%22security%20bug%22%20) in the bug tracker. – jww May 11 '17 at 17:25
  • I'm not "latch to" anything. I just can't use a library with memory leaks. – c00000fd May 11 '17 at 18:48

3 Answers3

4

If you run code containing AES-NI instructions on x86 hardware which does not support these instructions, you should get invalid instruction errors. Unless the code does something smart (such as looking at CPUID to decide whether to run AES-NI optimized code, or something else), this can also be used to detect whether AES-NI instructions are actually used.

Otherwise you can always use a debugger, and set breakpoints at the AES-NI instructions to see whether your process ever uses that portion of code.

According to Crypto++ release notes AES-NI support was added in version 5.6.1. Looking at the source code of version 5.6.5 Crypto++, if AES-NI support was enabled at compile time, then it uses run-time checks (the HasAESNI() function, probably utilizing CPUID) to decide whether to use these intrinsics. See rijndael.cpp (and cpu.cpp for the CPUID code) in its source code for details.

jotik
  • 17,044
  • 13
  • 58
  • 123
  • Well, yeah, that's what I'm asking. Does Crypto++ library do any of that? – c00000fd May 08 '17 at 20:57
  • @c00000fd Updated my answer (also with links to source code). – jotik May 08 '17 at 21:07
  • I just set a breakpoint on `DetectX86Features` method in `cpu.cpp` (which btw you have a wrong link to) and it never triggered when I was doing AES/GCM encryption/decryption. I was able to trigger my breakpoint only when doing decryption for AES cyphertext in CBC_Mode (not related to this question.) So you mentioned "if AES-NI support was enabled at compile time" -- how do I do that? – c00000fd May 08 '17 at 22:04
  • @c00000fd - Be careful of inlining in release builds. But also see the answer below as to why you may not be breaking. – jww May 09 '17 at 00:32
  • @c00000fd There's most likely a better way to do it, but for one you could add a `#warning EVERYTHING IS OK` line after a `#if CRYPTOPP_BOOL_AESNI_INTRINSICS_AVAILABLE` line in [rijndael.cpp](https://github.com/weidai11/cryptopp/blob/master/rijndael.cpp) and see if that outputs the warning message during compilation. – jotik May 09 '17 at 08:24
3

I'm curious if the resulting compiled code will use Intel's AES-NI instructions?

Crypto++ 5.6.1 added support for AES-NI and Carryless Multiplies under GCM. It is used when two or three conditions are met. First, you are using a version of the library with the support. From the homepage under News (or the README):

  • 8/9/2010 - Version 5.6.1 released

    • added support for AES-NI and CLMUL instruction sets in AES and GMAC/GCM

Second, the compiler, assembler and the linker must support the instructions. For Crypto++, that means you use at least MSVC 2008 SP1, GCC 4.3, and Binutils 2.19. For MSVC, if you look at config.h, its guarded as follows (__AES__ is there for GCC and friends, too):

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

You can lookup _MSC_FULL_VER numbers at Visual Studio version. Ironically, I've never seen a similar page on MSDN even though the service packs matter. You have to go to a Chinese site. For example, checked iterators showed up in VS2005 SP1 (IIRC).

For Linux and GCC compatibles, the GNUmakefile checks the version of the compiler and assembler. If they are too old, then the makefile adds CRYPTOPP_DISABLE_AESNI to the command line to disable the support even if __AES__ is defined.

CRYPTOPP_DISABLE_AESNI shows up more often then you think. For example, if you download OpenBSD 6.0 (the current version), then CRYPTOPP_DISABLE_AESNI will be present because their assembler is so old. They are mostly stuck at the pre-GPL-2 version of their tools (apparently they did not agree to the license changes).

Third, the CPU supports both AES and SSE4 instructions (the reason for the SSE4 instructions is explained below). These checks are performed at runtime, and the function of interest is called HasAES() from cpu.h (there's also a 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;
}

The caveat of Item (3) is the library needed to be compiled with support from Item (2). If Item (2) did not include compile time support, then Item (3) cannot offer runtime support.

With respect to Item (3) and runtime support, we recently had to tune it. It seems some low-end Atom processors, like D2500's, have SSE2, SSE3, SSSE3 and AES-NI, but not SSE4.1 or SSE4.2. According to Intel ARK, its an optional configuration of the processor. We received one bug report about an illegal SSE4 instruction in the AES-NI codepath, so we had to add an HasSSE4() check. See PR 172, Check for SSE4 support before using SSE4.1 instruction.


And if so, what happens if the hardware (older CPU) does not support it?

Nothing. The default CXX implementation is used rather than the hardware accelerated AES.

You might be interested to know we also have other AES hardware acceleration, including ARMv8 Crypto and VIA Padlock. We also provide other hardware acceleration, like CRC32, Carryless-Multiplies and SHA. They all function the same way - compile time support is translated into runtime support.


(Comment): I just set a breakpoint on DetectX86Features method in cpu.cpp ... and it never triggered ...

This can be tricky for two reasons. First, the calls may be inlined in release builds so the code is shaped a little differently then you would expect.

Second, there's a global random number generator accessed by GlobalRNG(). GlobalRNG() is AES in OFB mode. When initializers run for the test.cpp translation unit, the GlobalRNG() is created which causes DetectX86Features() to run very early (before control enters main).

You may have better luck with observing the low level details with WinDbg.


Its also worth mentioning that AES/GCM can be sped up by interleaving AES with GCM. I believe the idea is to perform 4 rounds of AES key calculation and 1 CLMUL in parallel. Crypto++ does not take advantage of it, but OpenSSL takes the opportunity. I don't know what Botan or mbedTLS do.

jww
  • 97,681
  • 90
  • 411
  • 885
  • Thanks for the info. Couple points tho. 1) The Crypto++ 5.6.2 source I'm testing it with is compiled under `Debug` config, on Windows, with VS2008 in x64 project. 2) Yeah, for some reason a breakpoint on `HasAESNI` is never raised. (I'm testing it with VS2008 debugger.) I updated my original question with the code I'm testing it with. I also tried, and `CRYPTOPP_BOOL_AESNI_INTRINSICS_AVAILABLE` preprocessor variable is defined in my test project, while `CRYPTOPP_DISABLE_AESNI` is not. So what am I missing? Do I need to test a `Release` build instead? – c00000fd May 09 '17 at 20:56
  • *"So what am I missing?"* - with respect to what? You asked when the library uses AES-NI instructions. If you want to verify the instructions are available during an audit, then use `dumpbin` to disassemble the `rijndael.obj` object file. The library has tests that do the same under Linux: [`cryptest.sh : 1150`](https://github.com/weidai11/cryptopp/blob/master/cryptest.sh#L1150). We are looking for someone to help with a similar test for Windows: [Issue 159, Need cryptest.cmd for Windows testing](https://github.com/weidai11/cryptopp/issues/159). – jww May 10 '17 at 06:06
  • What I meant there is that I couldn't reproduce your instructions. So, OK, I'll try to run a release build thru a lower level debugger (IDA-Pro & WinDbg.) Can you clue me in -- what method inside `EncryptAndAuthenticate` for `GCM::Encryption` class shall I be looking for that calls those AES-NI instructions? As for helping to test the cmd, sure, I'll take a look. But first I need to resolve this question. – c00000fd May 10 '17 at 19:22
  • You state you are using *"Visual Studio 2008"*. We state you need *"Visual Studio 2008 SP1"* (that's `_MSC_FULL_VER >= 150030729`). You have not provided a [`dumpbin /disasm`](https://msdn.microsoft.com/en-us/library/xtf7fdaz.aspx), so its hard to say what's going on with code generation. The best I can tell, everything is working as expected because the compiler you are using does not support AES-NI. – jww May 10 '17 at 20:15
  • OK. Yes, you found the problem. I'm not using SP1 because it bloats the code an extra `MB` or so. Crap!!! I'll try compiling the static libs with `VS2008 SP1` but I doubt that they'd link to a project built without SP1. – c00000fd May 11 '17 at 03:38
  • Let me ask, why are you limiting it to VS2008 SP1? Is it the AES-NI intrinsics that are missing in VS2008 w/o SP1? – c00000fd May 11 '17 at 03:41
  • @c00000fd - *"... why are you limiting it to VS2008 SP1... "* - We don't limit it. Microsoft made that choice for their compiler and assembler. See, for example, [AES Intrinsics](https://msdn.microsoft.com/en-us/library/cc714206(v=vs.90).aspx) on MSDN and [Compiling with AES-NI in MS Visual Studio 2008/2010](https://software.intel.com/en-us/forums/intel-isa-extensions/topic/287212). – jww May 11 '17 at 12:51
  • @c00000fd - *"[Are] ... AES-NI intrinsics that are missing in VS2008 w/o SP1"* - Yes, they are missing in VS2008 and below. You must use VS2008 SP1 or above. From [MSDN](https://msdn.microsoft.com/en-us/library/cc714206(v=vs.90).aspx): *"The Visual C++ compiler included the following intrinsics in Microsoft Visual Studio 2008 SP1... [list of AES-NI intrinsics]"*. – jww May 11 '17 at 12:53
2

Just to finish up my question, here's my findings.

The method that forks the execution to hardware supported AES-NI instructions, vs software implemented ones in Crypto++ library for my code sample above, is Rijndael::Enc::AdvancedProcessBlocks located in rijndael.cpp. It starts as such:

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

The CRYPTOPP_BOOL_AESNI_INTRINSICS_AVAILABLE preprocessor variable will be defined if you're building the Crypto++ library with at least Visual Studio 2008 with SP1 (note that SP1 is important.) Such dependency is necessary to be able to use AES-NI intrinsics (such as _mm_aesenc_si128 and _mm_aesenclast_si128) to generate Intel's AES-NI machine code instructions.

So adding a breakpoint to the beginning of

enter image description here

will let you debug it right from the Visual Studio. No outside debugger needed.

If you then step into the AESNI_AdvancedProcessBlocks method the actual AES encryption will be processed in one of the AESNI_Enc_* methods. Here's how the actual aesenc and aesenclast machine instructions may look like for x86 configuration in the Release build:

enter image description here

So to answer my original question, for the code sample in my post above to be able to utilize Intel's AES-NI instructions one needs to build both the code sample and Crypto++ library with at least Visual Studio 2008 with SP1. (Just building it with Visual Studio 2008, or earlier version, will not do the job, even if the CPU that the code runs on supports AES-NI instructions.) After that, no other steps seem to be necessary. The library will detect the presence of AES-NI instructions automatically (HasAESNI() function) and will use them when available. Otherwise it will default to a software implementation.

Lastly, just from curiosity I decided to see how much difference would hardware vs software AES-GCM encryption would produce in speed. I used the following code snippet (from my code sample above):

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

Here are two results:

enter image description here

and

enter image description here

This is obviously not an all-encompassing test. I ran it on my desktop with the "Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz" CPU.

But the good news is that AES-GCM encryption seems to be very fast, even without a hardware AES support.

Community
  • 1
  • 1
c00000fd
  • 20,994
  • 29
  • 177
  • 400