#c #openssl #encryption
#c #openssl #шифрование
Вопрос:
Я играю с подпрограммами OpenSSL EVP для дешифрования, используя режим AES 128 cbc.
Я использую тестовые векторы, указанные на сайте NIST, для тестирования моей программы.
Похоже, что программа завершается сбоем при выполнении процедуры EVP_DecryptFinal_ex.
Кто-нибудь, пожалуйста, может сказать мне, в чем проблема?
Также, как мне выполнить проверку ошибок здесь, чтобы выяснить, почему эта процедура завершается с ошибкой?
ОБНОВЛЕНО:
Пожалуйста, проверьте приведенный ниже код. Я добавил часть шифрования и дешифрования. Шифрование работает. Но во время дешифрования, хотя результаты обоих совпадают, шестнадцатеричное значение шифра кажется 80 байтами в отличие от ожидаемых 64 байт (упомянутых в NIST), хотя дешифрование работает, и расшифрованный текст соответствует открытому тексту! Кто-нибудь может уточнить?
Ожидаемое значение зашифрованного текста должно быть:
cipher: 0000 76 49 ab ac 81 19 b2 46 ce e9 8e 9b 12 e9 19 7d
0010 50 86 cb 9b 50 72 19 ee 95 db 11 3a 91 76 78 b2
0020 73 be d6 b8 e3 c1 74 3b 71 16 e6 9e 22 22 95 16
0030 3f f1 ca a1 68 1f ac 09 12 0e ca 30 75 86 e1 a7
вот код:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/evp.h>
int AES_BLOCK_SIZE;
int main(int argc, char **argv)
{
EVP_CIPHER_CTX en;
EVP_CIPHER_CTX de;
EVP_CIPHER_CTX_init(amp;en);
EVP_CIPHER_CTX_init(amp;de);
const EVP_CIPHER *cipher_type;
unsigned char *mode;
unsigned char *passkey, *passiv, *plaintxt;
int vector_len = 0;
char *plain;
char *plaintext;
unsigned char *ciphertext;
int olen, len;
int i =0;
//NIST VALUES TO CHECK
unsigned char iv[] =
{ 0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0 };
unsigned char key[] =
{ 0x2b, 0x7e, 0x15, 0x16,
0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88,
0x09, 0xcf, 0x4f, 0x3c , 0 };
unsigned char input[] =
{ 0x6b, 0xc1, 0xbe, 0xe2,
0x2e, 0x40, 0x9f, 0x96,
0xe9, 0x3d, 0x7e, 0x11,
0x73, 0x93, 0x17, 0x2a,
0xae, 0x2d, 0x8a, 0x57,
0x1e, 0x03, 0xac, 0x9c,
0x9e, 0xb7, 0x6f, 0xac,
0x45, 0xaf, 0x8e, 0x51,
0x30, 0xc8, 0x1c, 0x46,
0xa3, 0x5c, 0xe4, 0x11,
0xe5, 0xfb, 0xc1, 0x19,
0x1a, 0x0a, 0x52, 0xef,
0xf6, 0x9f, 0x24, 0x45,
0xdf, 0x4f, 0x9b, 0x17,
0xad, 0x2b, 0x41, 0x7b,
0xe6, 0x6c, 0x37, 0x10, 0 };
printf("AES ALGORITHM FOR 128 bit CBC MODEn");
cipher_type = EVP_aes_128_cbc();
AES_BLOCK_SIZE = 128;
passkey = key;
passiv = iv;
plain = input;
printf("iv=");
for(i = 0; i < sizeof iv; i ){
printf("x", iv[i]);
}
printf("n");
printf("key=");
for(i = 0; i < sizeof key; i ){
printf("x", key[i]);
}
printf("n");
printf("Initializing AES ALGORITHM FOR CBC MODE..n");
EVP_EncryptInit_ex(amp;en, cipher_type, NULL, passkey, passiv);
EVP_DecryptInit_ex(amp;de, cipher_type, NULL, passkey, passiv);
olen = len = strlen(input) 1;
printf("len value before aes_encrypt "%d"n", len);
int c_len = len AES_BLOCK_SIZE - 1;
int f_len = 0;
ciphertext = (unsigned char *)malloc(c_len);
if(!EVP_EncryptInit_ex(amp;en, NULL, NULL, NULL, NULL)){
printf("ERROR in EVP_EncryptInit_ex n");
return NULL;
}
if(!EVP_EncryptUpdate(amp;en, ciphertext, amp;c_len, plain, len)){
printf("ERROR in EVP_EncryptUpdate n");
return NULL;
}
printf("strlen value of ciphertext after update "%d"n", strlen(ciphertext));
if(!EVP_EncryptFinal_ex(amp;en, ciphertext c_len, amp;f_len)){
printf("ERROR in EVP_EncryptFinal_ex n");
return NULL;
}
printf("strlen value of ciphertext after final "%d"n", strlen(ciphertext));
EVP_CIPHER_CTX_cleanup(amp;en);
len = c_len f_len;
printf("len value after aes_encrypt "%d"n", len);
len = strlen(ciphertext);
printf("strlen value of ciphertext after aes_encrypt "%d"n", len);
int p_len = len;
f_len = 0;
plaintext = (unsigned char *)malloc(p_len);
//memset(plaintext,0,sizeof(plaintext));
if(!EVP_DecryptInit_ex(amp;de, NULL, NULL, NULL, NULL)){
printf("ERROR in EVP_DecryptInit_ex n");
return NULL;
}
EVP_CIPHER_CTX_set_padding(amp;de, 0);
if(!EVP_DecryptUpdate(amp;de, plaintext, amp;p_len, ciphertext, len)){
printf("ERROR in EVP_DecryptUpdaten");
return NULL;
}
if(!EVP_DecryptFinal_ex(amp;de, plaintext p_len, amp;f_len)){
printf("ERROR in EVP_DecryptFinal_exn");
return NULL;
}
EVP_CIPHER_CTX_cleanup(amp;de);
len = p_len f_len;
printf("Decrypted value = %sn", plaintext);
printf("len value after aes_decrypt "%d"n", len);
if (strncmp(plaintext, input, olen))
printf("FAIL: enc/dec failed for "%s"n", input);
else
printf("OK: enc/dec ok for "%s"n", plaintext); // "%s"n
printf("OK: ciphertext is "%s"n", ciphertext); // "%s"n
printf("n");
unsigned char *s3 = ciphertext;
printf("s3 =n");
int nc = 0;
while(*s3 != ''){
printf("x", *s3);
s3 ;
nc ;
if(nc == 16){
printf("n");
nc = 0;
}
}
printf("n");
//printf("nc = %dn", nc);
free(ciphertext);
free(plaintext);
return 0;
}
Ответ №1:
Точно так же, как вам нужно сопоставить ключ и IV при шифровании и дешифровании, вам также необходимо сопоставить настройку заполнения. Тесты NIST не дополнены. Вот выдержка из документации OpenSSL:
EVP_DecryptInit_ex(), EVP_DecryptUpdate() и EVP_DecryptFinal_ex() являются соответствующими операциями дешифрования. EVP_DecryptFinal() вернет код ошибки, если заполнение включено и конечный блок неправильно отформатирован. Параметры и ограничения идентичны операциям шифрования, за исключением того, что, если включено заполнение, в буфере расшифрованных данных, передаваемом в EVP_DecryptUpdate(), должно быть достаточно места для байтов (inl cipher_block_size), если размер блока шифрования не равен 1, и в этом случае достаточно байтов inl.
Выполнив поиск на той же странице для «заполнения», вы увидите функцию EVP_CIPHER_CTX_set_padding
:
EVP_CIPHER_CTX_set_padding() включает или отключает заполнение. По умолчанию операции шифрования дополняются с использованием стандартного заполнения блока, а заполнение проверяется и удаляется при расшифровке. Если параметр pad равен нулю, то заполнение не выполняется, общий объем зашифрованных или расшифрованных данных должен быть кратен размеру блока, иначе произойдет ошибка.
Итак, в какой-то момент после вашего вызова EVP_CIPHER_CTX_init
и перед началом расшифровки вам нужно сделать это:
EVP_CIPHER_CTX_set_padding(amp;de, 0);
Комментарии:
1. Если я не задаю заполнение, stderr приводит меня к строке: EVPerr (EVP_F_EVP_DECRYPTFINAL_EX,EVP_R_WRONG_FINAL_BLOCK_LENGTH); И если я задаю заполнение, как указано выше (равное нулю), я получаю ошибку: EVPerr (EVP_F_EVP_DECRYPTFINAL_EX, EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH); Но как это исправить? Нигде в процедурах дешифрования я не упоминаю длину блока или мне следует?
2. Но, согласно вашему объяснению, заполнение не установлено, поэтому мне вообще не нужна функция set padding, и все же расшифровка должна работать правильно? Я отредактировал код. Не могли бы вы, пожалуйста, проверить сейчас??
3. @pimmling: EVP_R_WRONG_FINAL_BLOCK_LENGTH означает, что он искал дополнение, но не нашел его, поэтому вы это исправили. Теперь вы видите, что длина данных AES должна составлять 16 байт. Вы не работаете со строками, заканчивающимися нулем. Вы работаете с двоичными данными. Удалите нулевой ограничитель в конце всего. Не используйте strlen(), потому что это не строка, заканчивающаяся нулем. В вашем случае используйте sizeof(). Просто передавайте дешифратору 16 байт за раз, и это сработает, и вам нужно будет выяснить, как разделить большие фрагменты данных на 16-байтовые фрагменты.
4. Я использовал sizeof и удалил strlen везде, где это было необходимо. Однако из других сообщений я понимаю, что интерфейс EVP для Decryot update выполняет разделение данных на подходящие фрагменты .. не могли бы вы, пожалуйста, объяснить?
5. @pimmling: Да, вы правы,
EVP_DecryptUpdate
сделает все это за вас, если у вас есть все данные в памяти, и вам не нужно разделять их самостоятельно.
Ответ №2:
Чтобы отобразить ошибки после сбоя функции OpenSSL, вы можете использовать:
ERR_print_errors_fp(stderr);
Ответ №3:
У меня была такая же проблема с процедурой EVP_DecryptFinal_ex. Я обнаружил, что вы не получите длину зашифрованного текста с помощью, strlen(ciphertext)
потому что функция strlen()
возвращает длину строки C.
Зашифрованный текст после шифрования может содержать символы ‘ 0’, которые считаются концом строки C, поэтому вы не получите правильную длину зашифрованного текста с помощью функции strlen()
.
Вместо этого вы должны запомнить длину зашифрованного текста после шифрования. В вашей программе вы делаете это с помощью c_len
и f_len
:
if(!EVP_EncryptUpdate(amp;en, ciphertext, amp;c_len, plain, len)){
printf("ERROR in EVP_EncryptUpdate n");
return NULL;
}//Here you get length of ciphertext in c_len
printf("strlen value of ciphertext after update "%d"n", strlen(ciphertext));
if(!EVP_EncryptFinal_ex(amp;en, ciphertext c_len, amp;f_len)){
printf("ERROR in EVP_EncryptFinal_ex n");
return NULL;
}//Here you get the rest of padded ciphertext in f_len
//This printf won't print out the real lengt of ciphertext you should put in (c_len f_len)
printf("strlen value of ciphertext after final "%d"n", strlen(ciphertext));
EVP_CIPHER_CTX_cleanup(amp;en);
len = c_len f_len;//This is the real length of ciphertext
printf("len value after aes_encrypt "%d"n", len);
len = strlen(ciphertext);//And here you rewrite it, delete this line and you should get it right
Другое дело, когда вы что для распечатки зашифрованного текста не используете:
printf("OK: ciphertext is "%s"n", ciphertext);
«%s» также рассматривается как строка C и может выводить только часть всего зашифрованного текста. Используйте вместо:
int i = 0;
printf("nCiphertext:");
for(i = 0; i < len; i )//variable len is length of ciphertext memorized after encryption.
{printf("%c",ciphertext[i]);}
Комментарии:
1. Для распечатки зашифрованного текста я предлагаю использовать этот метод:
BIO_dump_fp(stdout, ciphertext, length);
Это форматирует выходные данные точно так же, как в шестнадцатеричных редакторах.