Hi,
I'm using AES 128 GCM with TLS 1.2 and trying to understand the AES key expansion code for decrypting received SSL records.
I'm not an expert on AES but as I understand it, we use the IV (4 byte salt + 8 byte explicit nonce in the received message), pad to 16 bytes, increment and use this as input to the AES key expansion for the first block of ciphertext. This produces a round key per AES round (10 rounds for AES 128). We then increment our IV as input to the key expansion and generate the rounding keys for the next block.
I noticed aesni_setkey_enc_128 in aesni.c contains the Intel AES-NI instruction AESKEYGENASSIST which helps with key expansion.
However, what's confusing me is when I add a breakpoint in GDB, this function is only called once, via mbedtls_ctr_drbg_seed in ctr_drbg.c. I thought we need to do the key expansion on every block, to generate the round keys?
I kept looking at the code and I noticed mbedtls_aesni_crypt_ecb, which contains the Intel AES-NI instructions for performing decryption.
This loads the round key via ctx->buf + ctx->rk_offset but I do not see any code updating this round key per block.
Could someone please explain where the round keys are generated for each round, per block?
Thanks,
Hello,
A TLS session uses one key pair in each direction. Each block is encrypted and authenticated with a separate IV, but with the same key. When MBEDTLS_USE_PSA_CRYPTO is enabled, each encryption/decryption operation performs the key expansion. When MBEDTLS_USE_PSA_CRYPTO is disabled, encryption/decryption reuses a context which contains the expanded key, so there are fewer calls to key expansion. In all cases, there are at least two calls to key expansion either near the end of the handshake or for the first application data exchange.
By default, on x86, Mbed TLS will use AESNI (via aesni.c) if present. If AESNI is enabled at compile time and present at runtime, it is always used.
If GDB isn't breaking on any aesni_setkey_enc_128 calls from the TLS code, I can think of two explanations. One is that the compiler has inlined several copies of this function and GDB is only breaking on one of the copies. Another is that TLS might not be using AES-128 after all — maybe it's using AES-256 or Chacha-Poly? Please double-check which cipher suite is used, e.g. with debugging traces or Wireshark.
Best regards,
Hi, thank you for taking the time to reply.
I added attribute noinline to the function generating the key schedule, aesni_setkey_enc_128() and rebuilt. However, it is still only called twice in total. I also added a breakpoint in aes.c:547, which looks like the non AES-NI code to generate the key schedule? That was never called.
My ciphersuite is TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256, according to mbedtls_ssl_get_ciphersuite(mbedtls_ssl_context).
Besides adding break points I have also stepped through the code, line by line. Please correct me if my understanding is wrong here:
mbedtls_ssl_decrypt_buf() extracts the 4 byte salt, which is stored within transform->iv_dec? mbedtls_ssl_decrypt_buf() also sets the 8 byte explicit_nonce to variable dynamic_iv? The same function then passes these two variables to ssl_build_record_nonce(), which creates the 12-byte IV?
Eventually we reach mbedtls_gcm_crypt_and_tag(), which calls mbedtls_gcm_starts()
mbedtls_gcm_starts() copies the 12 byte IV in to a 16 byte within mbedtls_gcm_context->y and increments by 1. This is the IV value required to generate the key schedule of the first block of ciphertext.
The remaining call stack to the point of decryption is:
mbedtls_gcm_starts() mbedtls_cipher_update() ctx->cipher_info->base->ecb_func() aes_crypt_ecb_wrap() mbedtls_aes_crypt_ecb() mbedtls_aesni_crypt_ecb()
mbedtls_aesni_crypt_ecb() loads the round key via (ctx->buf + ctx->rk_offset) to decrypt the first block of ciphertext.
However, I could not see any of this path calling a function which generated a key schedule.
I must be wrong (because it works) but if you could show where it is happening, that would be greatly appreciated.
Just to repeat (in case my understanding is wrong) I am expecting 11 keys to be generated before every block of 16 bytes is decrypted.
aesni_setkey_enc_128() is called twice for each connection (assuming MBEDTLS_USE_PSA_CRYPTO is not enabled and no renegociation happens): once for the outgoing key and once for the incoming key. This happens before the call to mbedtls_gcm_starts(). The RNG does not call aesni_setkey_enc_128() since it uses AES-256 by default.
In the default configuration, on x86_64 with AESNI, for mbedtls-3.3.0, running ssl_client2 force_ciphersuite=TLS-ECDHE-RSA-WITH-AES-128-GCM-SHA256, here's the call stack at the point where the key schedule starts (copied from gdb on Linux):
0 in aesni_setkey_enc_128 of source/library/aesni.c:277 1 in mbedtls_aesni_setkey_enc of source/library/aesni.c:453 2 in mbedtls_aes_setkey_enc of source/library/aes.c:545 3 in aes_setkey_enc_wrap of source/library/cipher_wrap.c:193 4 in mbedtls_cipher_setkey of source/library/cipher.c:328 5 in mbedtls_gcm_setkey of source/library/gcm.c:147 6 in gcm_aes_setkey_wrap of source/library/cipher_wrap.c:504 7 in mbedtls_cipher_setkey of source/library/cipher.c:328 8 in ssl_tls12_populate_transform of source/library/ssl_tls.c:8364 9 in mbedtls_ssl_derive_keys of source/library/ssl_tls.c:6337 10 in ssl_write_certificate_verify of source/library/ssl_tls12_client.c:3423 11 in mbedtls_ssl_handshake_client_step of source/library/ssl_tls12_client.c:3725 12 in mbedtls_ssl_handshake_step of source/library/ssl_tls.c:3730 13 in mbedtls_ssl_handshake of source/library/ssl_tls.c:3795 14 in main of source/programs/ssl/ssl_client2.c:2280
Then a second call with 8 in ssl_tls12_populate_transform of source/library/ssl_tls.c:8372 for the key in the other direction.
Best regards,
Gilles Peskine via mbed-tls mbed-tls@lists.trustedfirmware.org wrote: > If GDB isn't breaking on any aesni_setkey_enc_128 calls from the TLS code, I > can think of two explanations. One is that the compiler has inlined several > copies of this function and GDB is only breaking on one of the > copies.
GDB is pretty good about getting them all (with the right data from the compiler).
> Another is that TLS might not be using AES-128 after all — maybe it's > using AES-256 or Chacha-Poly? Please double-check which cipher suite is used, > e.g. with debugging traces or Wireshark.
This seems more likely.
I have also added logging on all 3x AES-NI key schedule functions, along with what I think is the non-AES-NI in aes.c:547.
The 256 bit version is called 8x times at the beginning. The 128 bit is called 2x times at the beginning, but after neither of all 4 functions are called (and I am receiving data and it's getting decrypted).
I am genuinely puzzled (and intrigued).
mbed-tls@lists.trustedfirmware.org