I am trying to understand the working of OpenSSL's EVP_EncryptUpdate function, specifically why does it return different cipher-text for same plaintext, key and IV (initially provided), over multiple calls. As far as I understand, the IV should be changed after each call, and the function takes care of the IV internally. The Question is how exactly is the IV handled internally? Trying to fetch the IV returns the same IV as the one the context is initialised with.
#include <openssl/ssl.h>
void print_array(char *title, unsigned char *buff, int len)
{
printf("%s:\n", title);
for(int i = 0; i<len; ++i)
printf("%02x ", buff[i]);
printf("\n");
}
void handleErrors()
{
printf("Error in encryption/decryption\n");
}
void encrypt_EVP_aes_256_gcm_init(EVP_CIPHER_CTX **ctx, unsigned char *key, unsigned char *iv)
{
if(!(*ctx = EVP_CIPHER_CTX_new()))
handleErrors();
if(1 != EVP_EncryptInit_ex(*ctx, EVP_aes_256_gcm(), NULL, key, iv))
handleErrors();
}
void encrypt(EVP_CIPHER_CTX *ctx, unsigned char *plaintext, int plaintext_len, unsigned char *ciphertext, int *ciphertext_len)
{
int len;
if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
handleErrors();
*ciphertext_len = len;
}
void test(const unsigned char* key, const unsigned char* iv, const unsigned char *data, int iters)
{
unsigned char enc_data[32];
int32_t cipherLength;
EVP_CIPHER_CTX *encrypt_ctx;
encrypt_EVP_aes_256_gcm_init(&encrypt_ctx, key, iv);
for(int i = 0; i<iters; ++i)
{
print_array("iv", EVP_CIPHER_CTX_iv(encrypt_ctx), 12);
encrypt(encrypt_ctx, data, 32, enc_data, &cipherLength);
print_array("enc data", enc_data, cipherLength);
}
}
int main()
{
const unsigned char* key = "\x3c\x57\x5e\x25\x5f\x43\x41\x69\x3d\x5e\x48\x29\x72\x54\x27\x55\x3e\x29\x28\x65\x31\x34\x4a\x3e\x52\x2f\x7c\x6a\x7b\x25\x78\x52";
const unsigned char* iv = "\x75\x71\x71\x55\x36\x33\x59\x52\x2c\x22\x74\x7d\x6f\x36\x6d\x2e";
const unsigned char data[32] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
test(key, iv, data, 3);
return 0;
}
I tried outputting the IV, but it does not seem to display the running IV. I only get back the supplied IV.
If your C code is executed, the following ciphertexts result for the three iterations:
i = 0: 0x7e81d12cb6cd69e538f709f69274f7f397c375b460cae4f6433b556bd0b0f839
i = 1: 0x97c4c7be1b67c61975fe97288599b1029984ba05633fefd942d98400d20829e1
i = 2: 0x861e3f9f54f44bce0d5afe3c9554bb503a7a675e398485693d00519111a152d9
These come about as follows:
0x75717155363359522c22747d
.0x00000001
are appended: 0x75717155363359522c22747d00000001
. The initial value determined in this way is reserved for the later determination of the GCM authentication tag and is not used for the encryption itself.0x75717155363359522c22747d00000002
.0x75717155363359522c22747d00000004
.0x75717155363359522c22747d00000006
and so on.I am not aware of any EVP functions that retrieve the initial value or intermediate counter values. However, as both the calculation of the initial value and the incrementation are carried out automatically, there is actually no need for this (as already mentioned in the 1st comment).
Regarding encryption, GCM works like CTR, with the above counters corresponding to the IVs. The correctness of the counters can therefore be easily verified by encrypting with CTR and the determined counters instead of GCM and the 12 bytes IV.
The C code can be adapted accordingly (for the sake of simplicity without exception handling):
void encrypt_with_CTR(unsigned char* key, unsigned char* iv, unsigned char* data)
{
int cipherLength;
unsigned char ciphertext[32];
EVP_CIPHER_CTX* encrypt_ctx = EVP_CIPHER_CTX_new();
EVP_EncryptInit_ex(encrypt_ctx, EVP_aes_256_ctr(), NULL, key, iv);
EVP_EncryptUpdate(encrypt_ctx, ciphertext, &cipherLength, data, 32);
print_array((char*)"ciphertext: ", ciphertext, cipherLength);
}
...
unsigned char data[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
unsigned char key[] = "\x3c\x57\x5e\x25\x5f\x43\x41\x69\x3d\x5e\x48\x29\x72\x54\x27\x55\x3e\x29\x28\x65\x31\x34\x4a\x3e\x52\x2f\x7c\x6a\x7b\x25\x78\x52";
unsigned char iv_1[] = "\x75\x71\x71\x55\x36\x33\x59\x52\x2c\x22\x74\x7d\x00\x00\x00\x02";
unsigned char iv_2[] = "\x75\x71\x71\x55\x36\x33\x59\x52\x2c\x22\x74\x7d\x00\x00\x00\x04";
unsigned char iv_3[] = "\x75\x71\x71\x55\x36\x33\x59\x52\x2c\x22\x74\x7d\x00\x00\x00\x06";
encrypt_with_CTR(key, iv_1, data);
encrypt_with_CTR(key, iv_2, data);
encrypt_with_CTR(key, iv_3, data);
Alternatively, this can be verified with CyberChef:
0x75717155363359522c22747d00000002
:
0x7e81d12cb6cd69e538f709f69274f7f397c375b460cae4f6433b556bd0b0f839
0x75717155363359522c22747d00000004
:
0x97c4c7be1b67c61975fe97288599b1029984ba05633fefd942d98400d20829e1
0x75717155363359522c22747d00000006
:
0x861e3f9f54f44bce0d5afe3c9554bb503a7a675e398485693d00519111a152d9
both of which, as expected, correspond to the ciphertexts of the GCM encryption with the 12 bytes IV.
Notes:
EVP_EncryptFinal_ex()
, which is why this function must be called at the end of the encryption process (see first comment). The tag must then be retrieved using EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag)
(see second comment).EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, NULL)
can be used to specify a different length for the IV than the recommended 12 bytes. In this case, the initial value of the counter is determined differently, namely using the GHASH algorithm.iv_len
).