C ++ OpenSSL API: как вычислить IV по умолчанию CLI из производной PBKDF2 - PullRequest
0 голосов
/ 06 мая 2020

Я пытаюсь реализовать дешифрование AES в одной из моих программ на C ++. Идея состоит в том, чтобы использовать следующую командную строку openSSL для генерации зашифрованного текста (но для расшифровки с помощью C ++ API): моя реализация в этом руководстве для реализации дешифрования: https://eclipsesource.com/blogs/2017/01/17/tutorial-aes-encryption-and-decryption-with-openssl/

Он работает хорошо, но использует устаревший алгоритм получения ключа, который я хочу заменить на PBKDF2.

Насколько я понимаю, тогда я должен использовать PKCS5_PBKDF2_HMAC(), а не EVP_BytesToKey(), предложенный в руководстве. Моя проблема в том, что EVP_BytesToKey смог получить как key , так и IV из соли и пароля, где PKCS5_PBKDF2_HMAC, кажется, только выводит по одному.

Я не смог найти больше информации / учебника о том, как получить как ключ , так и IV , и пробовал несколько реализаций, но не смог найти, как openSSL CLI генерирует IV. Я бы очень хотел избежать написания IV ни в CLI, ни в полезной нагрузке, реализация учебника была действительно удобной для этого.

Может ли кто-нибудь мне помочь?

Спасибо, лучше С уважением

1 Ответ

1 голос
/ 07 июня 2020

Я понимаю, что этому вопросу уже около месяца, но я натолкнулся на него в поисках информации о том, как сделать что-то подобное. Учитывая отсутствие ответов здесь, я обратился к источнику за ответами.

TL; DR (прямой ответ)

PKCS5_PBKDF2_HMAC() генерирует и ключ, и IV одновременно. Хотя он объединен в одну строку. Вы можете разделить строку на необходимые части.

const EVP_CIPHER *cipher = EVP_aes_256_cbc();
int iklen = EVP_CIPHER_key_length(cipher);
int ivlen = EVP_CIPHER_iv_length(cipher);
PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair);
memcpy(key, keyivpair, iklen);
memcpy(iv, keyivpair + iklen, ivlen);

Подробное описание

Прежде чем вдаваться в подробности, я чувствую, что должен упомянуть, что я использую C а не C ++. Однако я надеюсь, что предоставленная информация будет полезна даже для C ++.

Прежде всего, строку необходимо декодировать из base64 в приложении. После этого мы можем перейти к ключевому и IV поколению. Инструмент openssl указывает, что используется соль, начав зашифрованную строку со строки «Salted__», за которой следует 8 байтов соли (по крайней мере, для aes-256-cb c). Помимо соли нам также необходимо знать длину ключа и IV. К счастью, для этого есть вызовы API.

const EVP_CIPHER *cipher = EVP_aes_256_cbc();
int iklen = EVP_CIPHER_key_length(cipher);
int ivlen = EVP_CIPHER_iv_length(cipher);

Нам также нужно знать количество итераций (по умолчанию в openssl 1.1.1 при использовании -pbkdf2 это 10000 ), так как а также функцию дайджеста сообщения, которая в этом случае будет EVP_sha512() (как указано опцией -md sha512).

Когда у нас есть все вышеперечисленное, пора позвонить PKCS5_PBKDF2_HMAC().

PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair);

Краткая информация об аргументах

  1. pass имеет тип (const char *)
  2. длина пароля (int), если установлено -1, длина будет определена by strlen (pass)
  3. тип соли (const unsigned char *)
  4. длина соли (int)
  5. количество итераций (int)
  6. сообщение дайджест (const EVP_MD *), в данном случае возвращается EVP_sha512()
  7. общая длина ключа + iv (int)
  8. keyivpair (unsigned char *), здесь ключ и IV хранится

Теперь нам нужно разделить ключ и IV и сохранить их в отдельных переменных.

unsigned char key[EVP_MAX_KEY_LENGTH];  
unsigned char iv[EVP_MAX_IV_LENGTH];
memcpy(key, keyivpair, iklen);
memcpy(iv, keyivpair + iklen, ivlen);

И теперь у нас есть ключ и IV который можно использовать для расшифровки данных, зашифрованных с помощью инструмента openssl.

Po C

Для дальнейшего пояснения я написал следующее доказательство концепции (написано для Linux).

/*               
 * PoC written by zoke                                                        
 * Compiled with gcc decrypt-poc.c -o decrypt-poc -lcrypto -ggdb3 -Wall -Wextra
 */              
#include <stdio.h>                                                            
#include <stdlib.h>                                                           
#include <string.h>                                                           
#include <openssl/conf.h>                                                     
#include <openssl/evp.h>                                                      
#include <openssl/err.h>                                                      

void bail() {    
  ERR_print_errors_fp(stderr);                                                
  exit(EXIT_FAILURE);                                                         
}                

int main(int argc, char *argv[]) {
  if(argc < 3)   
    bail();      
  unsigned char key[EVP_MAX_KEY_LENGTH];  
  unsigned char iv[EVP_MAX_IV_LENGTH];
  unsigned char salt[8]; // openssl tool uses 8 bytes for salt
  unsigned char decodeddata[256];
  unsigned char ciphertext[256];
  unsigned char plaintext[256];
  const char *pass = argv[1]; // use first argument as password (PoC only)
  unsigned char *encodeddata = (unsigned char *)argv[2]; // use second argument
  int decodeddata_len, ciphertext_len, plaintext_len, len;

  // Decode base64 string provided as second option
  EVP_ENCODE_CTX *ctx;
  if(!(ctx = EVP_ENCODE_CTX_new()))
    bail();      
  EVP_DecodeInit(ctx);
  EVP_DecodeUpdate(ctx, decodeddata, &len, encodeddata, strlen((const char*)encodeddata));
  decodeddata_len = len;
  if(!EVP_DecodeFinal(ctx, decodeddata, &len))
    bail();      
  EVP_ENCODE_CTX_free(ctx);

  // openssl tool format seems to be 'Salted__' + salt + encrypted data
  // take it apart
  memcpy(salt, decodeddata + 8, 8); // 8 bytes starting at 8th byte
  memcpy(ciphertext, decodeddata + 16, decodeddata_len - 16); // all but the 16 first bytes
  ciphertext_len = decodeddata_len - 16;

  // Get some needed information
  const EVP_CIPHER *cipher = EVP_aes_256_cbc();
  int iklen = EVP_CIPHER_key_length(cipher);
  int ivlen = EVP_CIPHER_iv_length(cipher);
  int iter = 10000; // default in openssl 1.1.1
  unsigned char keyivpair[iklen + ivlen];                                     

  // Generate the actual key IV pair                                          
  if(!PKCS5_PBKDF2_HMAC(pass, -1, salt, 8, iter, EVP_sha512(), iklen + ivlen, keyivpair))
    bail();      
  memcpy(key, keyivpair, iklen);                                              
  memcpy(iv, keyivpair + iklen, ivlen);                                       

  // Decrypt data                                                             
  EVP_CIPHER_CTX *cipherctx;                                                  
  if(!(cipherctx = EVP_CIPHER_CTX_new()))                                     
    bail();      
  if(!EVP_DecryptInit_ex(cipherctx, cipher, NULL, key, iv))                   
    bail();      
  if(!EVP_DecryptUpdate(cipherctx, plaintext, &len, ciphertext, ciphertext_len))
    bail();      
  plaintext_len = len;                                                        
  if(!EVP_DecryptFinal_ex(cipherctx, plaintext + len, &len))                  
    bail();      
  plaintext_len += len;                                                       
  EVP_CIPHER_CTX_free(cipherctx);                                             
  plaintext[plaintext_len] = '\0'; // add null termination                    

  printf("%s", plaintext);                                                    

  exit(EXIT_SUCCESS);
}

Приложение протестировано при запуске

$ openssl aes-256-cbc -e -a -md sha512 -pbkdf2 -pass pass:test321 <<< "Some secret data"
U2FsdGVkX19ZNjDQXX/aACg7d4OopxqvpjclkaSuybeAxOhVRIONXoCmCQaG/Vg9
$ ./decrypt-poc test321 U2FsdGVkX19ZNjDQXX/aACg7d4OopxqvpjclkaSuybeAxOhVRIONXoCmCQaG/Vg9
Some secret data

Генерация ключа / IV, используемая инструментом командной строки, находится в apps / en c. c и был очень полезен, когда выяснял это.

...