Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Hacl_AES_128_GCM_NI and Hacl_AES_128_GCM_M32 #418

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
73 changes: 63 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ endif()
# - SOURCES_std: All regular files
# - SOURCES_vec128: Files that require vec128 hardware
# - SOURCES_vec256: Files that require vec256 hardware
# - SOURCES_aesni_pclmul: Files that require aes-ni/pclmul hardware

# Remove files that require missing toolchain features
# and enable the features for compilation that are available.
Expand Down Expand Up @@ -284,6 +285,44 @@ if(TOOLCHAIN_CAN_COMPILE_VALE)
set(HACL_CAN_COMPILE_VALE 1)
endif()

if(TOOLCHAIN_CAN_COMPILE_AESNI_PCLMUL)
add_compile_options(
-DHACL_CAN_COMPILE_VEC128
franziskuskiefer marked this conversation as resolved.
Show resolved Hide resolved
)
set(HACL_CAN_COMPILE_AESNI_PCLMUL 1)

# # We make separate compilation units (objects) for each hardware feature
list(LENGTH SOURCES_aesni_pclmul SOURCES_AESNI_PCLMUL_LEN)

if(NOT SOURCES_AESNI_PCLMUL_LEN EQUAL 0)
set(HACL_AESNI_PCLMUL_O ON)
add_library(hacl_aesni_pclmul OBJECT ${SOURCES_aesni_pclmul})
target_include_directories(hacl_aesni_pclmul PRIVATE)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "i386|i586|i686|i86pc|ia32|x86_64|amd64|AMD64")
add_compile_options(
-DHACL_CAN_COMPILE_VEC256
franziskuskiefer marked this conversation as resolved.
Show resolved Hide resolved
)

if(MSVC)
# Nothing to do here. MSVC has it covered
else()
target_compile_options(hacl_aesni_pclmul PRIVATE
-msse2
-msse3
-msse4.1
-msse4.2
-maes
-mpclmul
)
endif(MSVC)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|arm64v8")
target_compile_options(hacl_aesni_pclmul PRIVATE
-march=armv8-a+crypto
)
endif()
endif()
endif()

if(TOOLCHAIN_CAN_COMPILE_INLINE_ASM)
message(STATUS "Detected inline assembly support")
set(HACL_CAN_COMPILE_INLINE_ASM 1)
Expand Down Expand Up @@ -348,6 +387,11 @@ if(TOOLCHAIN_CAN_COMPILE_VEC256 AND HACL_VEC256_O)
target_link_libraries(hacl PRIVATE $<TARGET_OBJECTS:hacl_vec256>)
endif()

if(TOOLCHAIN_CAN_COMPILE_AESNI_PCLMUL AND HACL_AESNI_PCLMUL_O)
add_dependencies(hacl hacl_aesni_pclmul)
target_link_libraries(hacl PRIVATE $<TARGET_OBJECTS:hacl_aesni_pclmul>)
endif()

# # Static library
add_library(hacl_static STATIC ${SOURCES_std} ${VALE_OBJECTS})

Expand All @@ -359,6 +403,10 @@ if(TOOLCHAIN_CAN_COMPILE_VEC256 AND HACL_VEC256_O)
target_sources(hacl_static PRIVATE $<TARGET_OBJECTS:hacl_vec256>)
endif()

if(TOOLCHAIN_CAN_COMPILE_AESNI_PCLMUL AND HACL_AESNI_PCLMUL_O)
target_sources(hacl_static PRIVATE $<TARGET_OBJECTS:hacl_aesni_pclmul>)
endif()

# Install
# # This allows package maintainers to control the install destination by setting
# # the appropriate cache variables.
Expand Down Expand Up @@ -399,12 +447,13 @@ install(DIRECTORY vale/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/vale
# # Install config.h
install(FILES build/config.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hacl)

# The CPU detection is used for testing and benchmarking
if(ENABLE_TESTS OR ENABLE_BENCHMARKS)
# CPU feature detection for tests
add_library(hacl_cpu_features OBJECT ${PROJECT_SOURCE_DIR}/cpu-features/src/cpu-features.c)
target_include_directories(hacl_cpu_features PUBLIC ${PROJECT_SOURCE_DIR}/cpu-features/include)
endif(ENABLE_TESTS OR ENABLE_BENCHMARKS)
# CPU feature detection for tests
franziskuskiefer marked this conversation as resolved.
Show resolved Hide resolved
add_library(hacl_cpu_features OBJECT ${PROJECT_SOURCE_DIR}/cpu-features/src/cpu-features.c)
target_include_directories(hacl_cpu_features PUBLIC ${PROJECT_SOURCE_DIR}/cpu-features/include)
add_dependencies(hacl hacl_cpu_features)
target_link_libraries(hacl PRIVATE $<TARGET_OBJECTS:hacl_cpu_features>)
add_dependencies(hacl_static hacl_cpu_features)
target_link_libraries(hacl_static PRIVATE $<TARGET_OBJECTS:hacl_cpu_features>)

# Add ecckiila for benchmarks
if(ENABLE_BENCHMARKS)
Expand All @@ -426,6 +475,11 @@ if(ENABLE_BENCHMARKS)
target_include_directories(digestif PUBLIC ${PROJECT_SOURCE_DIR}/third-party/digestif)
endif(ENABLE_BENCHMARKS)

# Add bearssl for benchmarks
if(ENABLE_BENCHMARKS)
include(${PROJECT_SOURCE_DIR}/third-party/bearssl/config.cmake)
add_library(bearssl OBJECT ${SOURCES_bearssl})
endif(ENABLE_BENCHMARKS)

# Testing
# It's only one binary. Everything else is done with gtest arguments.
Expand Down Expand Up @@ -465,11 +519,10 @@ if(ENABLE_TESTS)
target_compile_options(${TEST_NAME} PRIVATE /std:c++20)
endif(MSVC)

add_dependencies(${TEST_NAME} hacl hacl_cpu_features)
add_dependencies(${TEST_NAME} hacl)
target_link_libraries(${TEST_NAME} PRIVATE
gtest_main
hacl_static
hacl_cpu_features
nlohmann_json::nlohmann_json
)
target_include_directories(${TEST_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/cpu-features/include)
Expand Down Expand Up @@ -542,13 +595,13 @@ if(ENABLE_BENCHMARKS)
target_compile_options(${BENCH_NAME} PRIVATE /std:c++20)
endif(NOT MSVC)

add_dependencies(${BENCH_NAME} hacl hacl_cpu_features)
add_dependencies(${BENCH_NAME} hacl)
target_link_libraries(${BENCH_NAME} PRIVATE
hacl_static
ecckiila
blake2
digestif
hacl_cpu_features
bearssl
benchmark::benchmark
)
endforeach()
Expand Down
262 changes: 262 additions & 0 deletions benchmarks/aesgcm.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/*
* Copyright 2023 Cryspen Sarl
*
* Licensed under the Apache License, Version 2.0 or MIT.
* - http://www.apache.org/licenses/LICENSE-2.0
* - http://opensource.org/licenses/MIT
*/

#include "util.h"

#include "krml/internal/target.h"
#ifdef HACL_CAN_COMPILE_AESNI_PCLMUL
#include "Hacl_AES_128_GCM_NI.h"
#endif
#include "Hacl_AES_128_GCM_M32.h"
#include "EverCrypt_AEAD.h"
#include "../third-party/bearssl/bearssl_block.h"
#include "../third-party/bearssl/bearssl_hash.h"
#include "../third-party/bearssl/bearssl_aead.h"

static bytes key(16, 7);
static bytes nonce(12, 9);
static bytes mac(16, 0);

#ifdef HACL_CAN_COMPILE_AESNI_PCLMUL
static void
HACL_AES_128_GCM_NI_encrypt(benchmark::State& state)
{
bytes plaintext(state.range(0), 0x37);
bytes ciphertext(state.range(0) + 16, 0);

for (auto _ : state) {
Lib_IntVector_Intrinsics_vec128 *ctx = (Lib_IntVector_Intrinsics_vec128 *)KRML_HOST_CALLOC((uint32_t)352U, sizeof (uint8_t));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There needs to be an alloc function such that the caller/user doesn't need to know the magic numbers here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of a convenient way to add such function. I don't seem to be able to alloc/dealloc memory buffers at Low*-level, these functionalities are specific to this library which only utilizes NI and CT64 function from inside EverCrypt functions that the user are supposed to use instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can look at other alloc functions like

Hacl_Streaming_Keccak_state *Hacl_Streaming_Keccak_malloc(Spec_Hash_Definitions_hash_alg a)
or
Lib_IntVector_Intrinsics_vec128 *Hacl_Blake2s_128_blake2s_malloc(void)
.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented malloc/free for AES_GCM context on hacl side mamonet/hacl-star@c9880f1
I will update it for this library once the issue of verifying changes of EverCrypt.AEAD is solved.

Hacl_AES_128_GCM_NI_aes128_gcm_init(ctx, key.data());
Hacl_AES_128_GCM_NI_aes128_gcm_encrypt(ctx, plaintext.size(), ciphertext.data(), plaintext.data(), 0, NULL, nonce.size(), nonce.data());
KRML_HOST_FREE(ctx);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to the alloc, do we need a free function here.

}
}

BENCHMARK(HACL_AES_128_GCM_NI_encrypt)->Setup(DoSetup)->Apply(Range);

static void
HACL_AES_128_GCM_NI_aad(benchmark::State& state)
{
bytes aad(state.range(0), 0x37);

for (auto _ : state) {
Lib_IntVector_Intrinsics_vec128 *ctx = (Lib_IntVector_Intrinsics_vec128 *)KRML_HOST_CALLOC((uint32_t)352U, sizeof (uint8_t));
Hacl_AES_128_GCM_NI_aes128_gcm_init(ctx, key.data());
Hacl_AES_128_GCM_NI_aes128_gcm_encrypt(ctx, 0, mac.data(), NULL, aad.size(), aad.data(), nonce.size(), nonce.data());
KRML_HOST_FREE(ctx);
}
}

BENCHMARK(HACL_AES_128_GCM_NI_aad)->Setup(DoSetup)->Apply(Range);
#endif

static void
HACL_AES_128_GCM_M32_encrypt(benchmark::State& state)
{
bytes plaintext(state.range(0), 0x37);
bytes ciphertext(state.range(0) + 16, 0);

for (auto _ : state) {
uint64_t *ctx = (uint64_t *)KRML_HOST_CALLOC((uint32_t)3168U, sizeof (uint8_t));
Hacl_AES_128_GCM_M32_aes128_gcm_init(ctx, key.data());
Hacl_AES_128_GCM_M32_aes128_gcm_encrypt(ctx, plaintext.size(), ciphertext.data(), plaintext.data(), 0, NULL, nonce.size(), nonce.data());
KRML_HOST_FREE(ctx);
}
}

BENCHMARK(HACL_AES_128_GCM_M32_encrypt)->Setup(DoSetup)->Apply(Range);

static void
HACL_AES_128_GCM_M32_aad(benchmark::State& state)
{
bytes aad(state.range(0), 0x37);

for (auto _ : state) {
uint64_t *ctx = (uint64_t *)KRML_HOST_CALLOC((uint32_t)3168U, sizeof (uint8_t));
Hacl_AES_128_GCM_M32_aes128_gcm_init(ctx, key.data());
Hacl_AES_128_GCM_M32_aes128_gcm_encrypt(ctx, 0, mac.data(), NULL, aad.size(), aad.data(), nonce.size(), nonce.data());
KRML_HOST_FREE(ctx);
}
}

BENCHMARK(HACL_AES_128_GCM_M32_aad)->Setup(DoSetup)->Apply(Range);

static void
EverCrypt_AES128_GCM_encrypt(benchmark::State& state)
{
bytes plaintext(state.range(0), 0x37);
bytes ciphertext(state.range(0), 0);

for (auto _ : state) {
EverCrypt_AEAD_state_s* ctx;
EverCrypt_Error_error_code res = EverCrypt_AEAD_create_in(
Spec_Agile_AEAD_AES128_GCM, &ctx, key.data());

if (res != EverCrypt_Error_Success) {
state.SkipWithError("Could not allocate AEAD state.");
break;
}

EverCrypt_AEAD_encrypt(ctx,
nonce.data(),
nonce.size(),
NULL,
0,
plaintext.data(),
plaintext.size(),
ciphertext.data(),
mac.data());

EverCrypt_AEAD_free(ctx);
}
}

BENCHMARK(EverCrypt_AES128_GCM_encrypt)->Setup(DoSetup)->Apply(Range);

static void
EverCrypt_AES128_GCM_aad(benchmark::State& state)
{
bytes aad(state.range(0), 0x37);

for (auto _ : state) {
EverCrypt_AEAD_state_s* ctx;
EverCrypt_Error_error_code res = EverCrypt_AEAD_create_in(
Spec_Agile_AEAD_AES128_GCM, &ctx, key.data());

if (res != EverCrypt_Error_Success) {
state.SkipWithError("Could not allocate AEAD state.");
break;
}

EverCrypt_AEAD_encrypt(ctx,
nonce.data(),
nonce.size(),
aad.data(),
aad.size(),
NULL,
0,
NULL,
mac.data());

EverCrypt_AEAD_free(ctx);
}
}

BENCHMARK(EverCrypt_AES128_GCM_aad)->Setup(DoSetup)->Apply(Range);

#ifndef NO_OPENSSL
static void
OpenSSL_aes_128_gcm_encrypt(benchmark::State& state)
{
bytes plaintext(state.range(0), 0x37);
bytes ciphertext(state.range(0), 0);

for (auto _ : state) {
int out_len, unused_len;
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
int result = EVP_EncryptInit_ex2(
ctx, EVP_aes_128_gcm(), key.data(), nonce.data(), NULL);
if (result != 1) {
state.SkipWithError("");
EVP_CIPHER_CTX_free(ctx);
break;
}
result = EVP_EncryptUpdate(
ctx, ciphertext.data(), &out_len, plaintext.data(), plaintext.size());
if (result != 1) {
state.SkipWithError("");
EVP_CIPHER_CTX_free(ctx);
break;
}
result = EVP_EncryptFinal_ex(ctx, mac.data(), &unused_len);
if (result != 1 || unused_len != 0) {
state.SkipWithError("");
EVP_CIPHER_CTX_free(ctx);
break;
}
EVP_CIPHER_CTX_free(ctx);
}
}

BENCHMARK(OpenSSL_aes_128_gcm_encrypt)->Setup(DoSetup)->Apply(Range);

static void
OpenSSL_aes_128_gcm_aad(benchmark::State& state)
{
bytes aad(state.range(0), 0x37);

for (auto _ : state) {
int out_len, unused_len;
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
int result = EVP_EncryptInit_ex2(
ctx, EVP_aes_128_gcm(), key.data(), nonce.data(), NULL);
if (result != 1) {
state.SkipWithError("");
EVP_CIPHER_CTX_free(ctx);
break;
}
result = EVP_EncryptUpdate(
ctx, NULL, &out_len, aad.data(), aad.size());
if (result != 1) {
state.SkipWithError("");
EVP_CIPHER_CTX_free(ctx);
break;
}
result = EVP_EncryptFinal_ex(ctx, mac.data(), &unused_len);
if (result != 1 || unused_len != 0) {
state.SkipWithError("");
EVP_CIPHER_CTX_free(ctx);
break;
}
EVP_CIPHER_CTX_free(ctx);
}
}

BENCHMARK(OpenSSL_aes_128_gcm_aad)->Setup(DoSetup)->Apply(Range);
#endif

static void
BearSSL_CT64_AES128_GCM_encrypt(benchmark::State& state)
{
bytes plaintext(state.range(0), 0x37);

for (auto _ : state) {
br_aes_ct64_ctr_keys bc;
br_gcm_context gc;
br_aes_ct64_ctr_init(&bc, key.data(), key.size());
br_gcm_init(&gc, &bc.vtable, br_ghash_ctmul64);

br_gcm_reset(&gc, nonce.data(), nonce.size());
br_gcm_flip(&gc);
br_gcm_run(&gc, 1, plaintext.data(), plaintext.size());
br_gcm_get_tag(&gc, mac.data());
}
}

BENCHMARK(BearSSL_CT64_AES128_GCM_encrypt)->Setup(DoSetup)->Apply(Range);

static void
BearSSL_CT64_AES128_GCM_aad(benchmark::State& state)
{
bytes aad(state.range(0), 0x37);

for (auto _ : state) {
br_aes_ct64_ctr_keys bc;
br_gcm_context gc;
br_aes_ct64_ctr_init(&bc, key.data(), key.size());
br_gcm_init(&gc, &bc.vtable, br_ghash_ctmul64);

br_gcm_reset(&gc, nonce.data(), nonce.size());
br_gcm_aad_inject(&gc, aad.data(), aad.size());
br_gcm_get_tag(&gc, mac.data());
}
}

BENCHMARK(BearSSL_CT64_AES128_GCM_aad)->Setup(DoSetup)->Apply(Range);

BENCHMARK_MAIN();
Loading