Шифрование AES CBC под openSSL дает неожиданный результат
Я использую openSSL
Функции библиотеки для целей шифрования и дешифрования на основе AES-128/CBC.
Код показан ниже (не надо путать макросы THROW / EXIT, они просто gotos). Независимо от того, что возвращает функция, вы увидите printf
вывод для отслеживания того, что отправляется туда и обратно в OpenSSL:
enc_status_t aes_cipher_ext(uint8_t should_encrypt, enc_aes128_key_t *key, uint8_t *iv, void *in, uint32_t inlen, void *out, uint32_t outlen, uint32_t * outlen_act)
{
enc_status_t status;
buffer_t inbuf, outbuf;
int ok;
const uint32_t BUFSIZE = AES_BUFFERSIZE;
uint8_t *read_buf = NULL;
uint8_t *cipher_buf = NULL;
uint32_t blocksize;
int out_len;
uint32_t numRead;
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
/* initialize input read buffer */
status = buffer_init(&inbuf, in, inlen);
if (status != ENC_OK) EXIT();
status = buffer_init(&outbuf, out, outlen);
if (status != ENC_OK) EXIT();
/* initialize AES engine */
ok = EVP_CipherInit(ctx, EVP_aes_128_cbc(), key->data, iv, should_encrypt);
if(!ok) THROW(status = ENC_ERR_OPENSSL);
blocksize = EVP_CIPHER_CTX_block_size(ctx);
read_buf = malloc(BUFSIZE);
cipher_buf = malloc(BUFSIZE + blocksize);
out_len = BUFSIZE + blocksize;
while (TRUE) { /*lint !e716 see below */
/* read data and cipher */
status = buffer_read(&inbuf, read_buf, BUFSIZE, &numRead);
if (status != ENC_OK) EXIT();
printf("AES input: num read = %d\n", numRead);
dump_ram(read_buf, numRead);
if (should_encrypt)
{
ok = EVP_EncryptUpdate(ctx, cipher_buf, &out_len, read_buf, numRead);
}
else
{
ok = EVP_DecryptUpdate(ctx, cipher_buf, &out_len, read_buf, numRead);
}
status = buffer_write(&outbuf, cipher_buf, out_len);
printf("AES result bytes: ok=%d\n", ok);
dump_ram(cipher_buf, out_len);
if (status != ENC_OK) EXIT();
if (numRead < BUFSIZE)
{
break; /* this breaks the while */
}
}
/* handle last block */
if (should_encrypt)
{
ok = EVP_EncryptFinal_ex(ctx, cipher_buf, &out_len);
}
else
{
ok = EVP_DecryptFinal_ex(ctx, cipher_buf, &out_len);
}
printf("AES LAST: ok=%d\n", ok);
dump_ram(cipher_buf, out_len);
status = buffer_write(&outbuf, cipher_buf, out_len);
if (status != ENC_OK) EXIT();
*outlen_act = outbuf.act_len;
exit_label:
/* de allocate */
if (cipher_buf) free(cipher_buf);
if (read_buf) free(read_buf);
EVP_CIPHER_CTX_free(ctx);
return status;
}
Когда я кормлю функцию с
Key = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
IV = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
Plain = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
Я получаю вывод:
AES input: num read = 16
31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
AES result bytes: ok=1
85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29 <-- first result
AES LAST: ok=1
71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9 <-- second result
Подводя итог, я получаю 32-байтовый зашифрованный текст из 16-байтового простого текста:
C = 85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29 71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9
Q: Почему openSSL добавляет дополнительные 16 байтов? ИМО в этом случае не должно быть отступов...
Когда расшифровка выполняется с этим зашифрованным текстом 32 байта, я получаю, используя тот же код
AES input: num read = 32
85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29
71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9
AES result bytes: ok=1
31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
AES LAST: ok=1
Таким образом, я возвращаю исходный 16-байтовый простой текст с DecryptFinal, возвращающим ноль байтов (следовательно, нет дампа).
Для сравнения, когда я расшифровываю зашифрованный 32-байтовый текст с помощью этого инструмента, результат
31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
. . . . . . . . . . . . . . . .
В результате получается оригинальный текст (первая строка) с дополнительной строкой 0x10.
Попытка использовать тот же инструмент для шифрования моего обычного текста даст мне только зашифрованную строку размером 16 байтов, что совпадает с первыми 16 байтами, которые я получаю от openSSL:
85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29
Почему так? В чем проблема??
1 ответ
Это для прокладки. Функции дополняют открытый текст так, чтобы полученная длина была кратна размеру блока (16). Данные, которые уже кратны блоку, все еще дополняются, так как в противном случае было бы невозможно провести различие между заполнением, добавленным преднамеренно, и открытым текстом, который просто выглядит как допустимый отступ.
Обычный способ заполнения состоит в добавлении N байтов значения N. Так что либо один 0x01
байт два 0x02
байт и т. д., до шестнадцати 0x10
байтов для полного блока, как в вашем случае.
Из документации здесь:
Если заполнение включено (по умолчанию), то EVP_EncryptFinal_ex() шифрует "окончательные" данные, то есть любые данные, которые остаются в частичном блоке. Он использует стандартный блочный отступ (также известный как PKCS), как описано в разделе NOTES ниже.
и под примечаниями:
PKCS дополняет работу, добавляя n байтов заполнения со значением n, чтобы сделать общую длину зашифрованных данных кратной размеру блока. Заполнение всегда добавляется, поэтому, если данные уже кратны размеру блока n, он будет равен размеру блока. Например, если размер блока равен 8 и необходимо зашифровать 11 байтов, то будет добавлено 5 байтов заполнения со значением 5.
Но вы можете контролировать, если заполнение включено:
EVP_CIPHER_CTX_set_padding () включает или отключает заполнение. По умолчанию операции шифрования дополняются стандартным блочным заполнением, а заполнение проверяется и удаляется при расшифровке. Если параметр pad равен нулю, то заполнение не выполняется, общий объем зашифрованных или расшифрованных данных должен быть кратным размеру блока, иначе произойдет ошибка.