OpenSSL EVP API: как расшифровать зашифрованный файл с помощью файла с симметричным ключом

Привет, я работаю над C в Linux.

У меня есть запрос, связанный с расшифровкой симметричного ключа.

Я создал симметричный ключ, используя следующую команду.

openssl rand base64 512> sym.key

Используя этот ключ (sym.key), я зашифровал файл с помощью следующей команды.

openssl enc -aes-256-cbc -in temp.txt -out temp.enc -kfile sym.key

Создан зашифрованный файл temp.enc.

Теперь мне нужно использовать тот же ключ (sym.key) с API дешифрования EVP и расшифровать этот зашифрованный файл.

Может ли кто-нибудь предложить мне лучший подход для этого.

Вот код

unsigned char* decode (unsigned char *key, int len)
{

   BIO *b64, *bmem;

   char *buffer = (char *)malloc(len);
   memset (buffer, 0, len);

   b64 = BIO_new(BIO_f_base64());
   bmem = BIO_new_mem_buf(key, len);
   bmem = BIO_push(b64, bmem);

   BIO_read(bmem, buffer, len);

   BIO_free_all(bmem);

    return buffer;
}

void decrypt(char *file_name, char *key_file)
{   

    unsigned char *inbuff = NULL, *outbuff = NULL, *ckey = NULL;
    char *buff = NULL;
    unsigned int flen = 0, outlen2 = 0, outlen1 = 0, klen = 0;

    FILE *fp = NULL, *kfp = NULL;
        unsigned char iv[16] = {};

    fp = fopen (file_name, "r");

    if (NULL == fp)
    {
        printf ("Cannot open file : %s\n", file_name);
        exit(1);
    }

    fseek (fp, 0, SEEK_END);
    flen = ftell (fp);
    rewind (fp);

    kfp = fopen (key_file, "r");

    if (NULL == kfp)
    {
        printf ("Cannot open file : %s\n", key_file);
        exit(1);
    }

    fseek (kfp, 0, SEEK_END);
    klen = ftell (kfp);
    rewind (kfp);

    inbuff = (unsigned char *)malloc(flen);
    outbuff = (unsigned char *)malloc(flen * 2);
    ckey = (unsigned char *)malloc(klen);
    buff = (char *)malloc(klen);

    fread (inbuff, sizeof(char), flen, fp);
    fread (buff, sizeof(char), klen, kfp);

    ckey = decode(buff, klen);

    EVP_CIPHER_CTX ctx;

#if 1
    if (! EVP_DecryptInit (&ctx, EVP_aes_256_cbc(), ckey, iv))
    {
        ERR_print_errors_fp(stderr);
        EVP_CIPHER_CTX_cleanup(&ctx);
        printf ("Error in Init\n");
        exit(1);
    }

    if (! EVP_DecryptUpdate (&ctx, outbuff, &outlen1, inbuff, flen))
    {
        ERR_print_errors_fp(stderr);
        EVP_CIPHER_CTX_cleanup(&ctx);
        printf ("Error in Init\n");
        exit(1);
    }

    if (! EVP_DecryptFinal (&ctx, outbuff + outlen1, &outlen2))
    {
        ERR_print_errors_fp(stderr);
        EVP_CIPHER_CTX_cleanup(&ctx);
        printf ("Error in Init\n");
        exit(1);
    }

    EVP_CIPHER_CTX_cleanup(&ctx);
#endif

    free (inbuff);
    free (outbuff);
    free (ckey);
    fclose (fp);
    fclose (kfp);

    printf ("Outbuff:\n %s\n", outbuff);

}

Спасибо.


person Sai    schedule 18.12.2015    source источник
comment
Возможный дубликат дешифрования файла на C ++, который был зашифрован с помощью openssl -aes-128-cbc   -  person Leśny Rumcajs    schedule 18.12.2015
comment
Привет. Я не мог тебя достать. Не могли бы вы подробнее проинформировать меня о своем предложении.   -  person Sai    schedule 21.12.2015
comment
Существует множество примеров шифрования / дешифрования EVP. Как здесь: wiki.openssl.org/index.php/ Если у вас есть более конкретный вопрос и примерный код, буду рад помочь.   -  person Leśny Rumcajs    schedule 21.12.2015
comment
Спасибо за ваш быстрый ответ. Я уже приводил этот пример. Как мы видим, здесь используются комбинированные клавиши KEY и IV, которые жестко запрограммированы. Что именно меня беспокоит, я сгенерировал случайный ключ с помощью параметра rand в openSSL и сохранил результат как sym.key, как показано openssl rand base64 512 ›sym.key ** Используя этот ключевой файл, я зашифровал файл через командную строку как ** openssl enc -aes-256-cbc -in temp.txt -out temp.enc -kfile sym.key Теперь, используя тот же ключевой файл, я должен расшифровать файл через любой API EVP. Не могли бы вы мне в этом помочь?   -  person Sai    schedule 21.12.2015
comment
Если вы не хотите, чтобы ваш ключ был жестко закодирован (что является отличным подходом), тогда вашей программе придется читать из файла. Думаю, ты справишься сам. Затем вам нужно будет декодировать ваш файл из Base64 (который подробно описан здесь stackoverflow.com/questions/5288076/). Наконец, вы передаете параметр функции инициализации дешифрования и просто следуете оставшейся части первой ссылки.   -  person Leśny Rumcajs    schedule 22.12.2015
comment
Привет, попробовал приведенную ниже кодировку. Не могли бы вы рассказать мне свои комментарии. Найдите код в ответе ниже   -  person Sai    schedule 22.12.2015
comment
Вы должны добавить свой код в свой вопрос (отредактировать его), если это не окончательный ответ, и указать, в чем заключается ошибка.   -  person Leśny Rumcajs    schedule 22.12.2015


Ответы (1)


Используя этот ключ (sym.key), я зашифровал файл с помощью следующей команды.
openssl enc -aes-256-cbc -in temp.txt -out temp.enc -kfile sym.key

Не совсем. openssl enc -kfile читает первую и только первую строку файла и использует ее в качестве пароля, который отличается от ключа. enc имеет три варианта ввода пароля, после чего он запускает пароль со случайной солью через процесс получения ключа для получения фактического ключа и IV (для шифров, использующих IV, и AES-CBC делает ). Затем он записывает заголовок, содержащий соль, за которым следует зашифрованный текст, в выходной файл. Это называется шифрованием на основе пароля (PBE), а иногда и функцией вывода ключа на основе пароля (PBKDF).

В зависимости от того, что вы действительно хотите сделать, есть два подхода.

Чтобы расшифровать имеющийся у вас файл

Прочтите первую строку из sym.key (исключая терминатор строки) как символы, НЕ base64, и добавьте в нее соль с temp.enc по EVP_BytesToKey примерно так:

FILE * pwfile = fopen (key_file, "r"); 
if(!pwfile) error_handling 
char pwline [70]; 
fgets (pwline, sizeof pwline, pwfile);
int pwlen = strlen (pwline); 
if(pwlen==0 || pwline[pwlen-1]!='\n') error_handling
pwline[--pwlen] = '\0';

// Open file_name and read into inbuff for flen as you have now 
// Optionally confirm the first 8 bytes are "Salted__"
// If on Windows must fopen with mode "rb" to get all bytes correctly;
// on Unix it is clearer to specify this but not actually needed
// because on Unix binary files and text files are treated the same

unsigned char key [256/8]; // AES-256 key is 32 bytes
unsigned char iv [128/8]; // AES IV is 16 bytes (regardless of key)
EVP_BytesToKey(EVP_aes_256_cbc(), EVP_md5(), inbuff+8, /* the salt! */
    (unsigned char*)pwline, pwlen, 1, key, iv);

// Now continue as you have with EVP_Encrypt{Init,Update,Final}
// using key,iv except use buffer inbuff+16 for length flen-16 .
// (And do something with the output, which you don't now!)

Чтобы создать файл, который вам явно нужен

Чтобы использовать enc для создания файла, зашифрованного прямым ключом, вы должны передать его с параметром -K (верхний регистр K) в командной строке в шестнадцатеричном формате. Однако обрабатывать шестнадцатеричный код в вашей программе на C неудобно, поэтому я бы обработал его на стороне шифрования примерно так:

openssl rand 32 >sym.key # AES-256 key must be exactly 32 bytes, not more
openssl enc -aes-256-cbc -in temp.txt -out temp.enc \
  -K $(od -An -tx1 sym.key | sed 's/ //g') -iv 00000000000000000000000000000000

затем в вашей программе на C прочтите sym.key (по крайней мере, как двоичный в Windows) и используйте его как есть, с IV равным 16 0, как у вас сейчас.

В качестве альтернативы можно rand записать шестнадцатеричный код и использовать его как есть

openssl rand -hex 32 >sym.key
openssl enc -aes-256-cbc -in temp.txt -out temp.enc -K $(cat sym.key) -iv (same)

затем в вашей программе на C прочтите sym.key (как текстовую строку) и преобразуйте его из 64 шестнадцатеричных символов в unsigned char key [32].

Прочие моменты

Если вам действительно нужно было декодировать файл base64, вам не нужно сначала читать его в память, а затем вставлять b64BIO в memBIO; вы можете нажать b64BIO на fileBIO, который читает файл.

Вам не нужно выделять flen*2 для outbuff. Расшифрованный открытый текст никогда не будет длиннее зашифрованного текста. (Что для соленой формы PBE на самом деле flen-16, как указано выше). Это шифрование, которое может расширить данные, а затем только один блок (для AES, 16 байт).

Перед использованием рекомендуется проверять, malloc не возвращал ли NULL. Это редко случается в современных системах, но это может произойти, если даже такой простой код, как этот, вызывается из более крупной программы, и в какой-либо другой части программы есть ошибка, которая приводит к нехватке памяти, или, возможно, уязвимость, используемая с помощью атаки типа «отказ в обслуживании». .

Если вы хотите поддерживать большие файлы, которые не умещаются в доступной памяти или могут не поместиться, итеративно считайте фрагменты, загрузите каждый в DecryptUpdate, запишите результаты (которые будут отставать примерно на один блок), а при EOF вызовите DecryptFinal и выведите любой последний частичный блок.

person dave_thompson_085    schedule 22.12.2015
comment
Привет, Дэйв, Спасибо за ваши ценные решения. Это мне очень помогло. Я пробовал описанные выше сценарии, как вы предложили. Расшифровать удалось на 98%. Я столкнулся с ошибкой ниже. Используя эту логику EVP_BytesToKey (первое решение), я могу расшифровать файл. Но, кроме того, он выводит некоторые мусорные значения. Используя следующий подход с преобразованием шестнадцатеричного кода в беззнаковый символ. Он расшифровал весь файл. Но он не печатает первый символ. Не могли бы вы помочь мне в этом. - person Sai; 28.12.2015
comment
@Sai (1) у меня работает первый метод. Вы уверены, что используете только правильную часть outbuff, а именно outlen1+outlen2 байта? Любые байты после этого могут быть мусором, оставшимся до выделения буфера. В частности, за расшифрованными данными НЕ следует нулевой байт, поэтому рассматривать их как строку C с такими вещами, как strlen strcpy printf("%s") или std::string(outbuff), неправильно. std::string(outbuff,outlen1+outlen2) или printf("%.*s", outlen1+outlen2, outbuff) правы. ... - person dave_thompson_085; 30.12.2015
comment
... (2) Что касается второго из вашего очень расплывчатого описания, я могу предположить, что вы испортили IV, из-за чего первый байт (или несколько) расшифрованного текста будет отображаться неправильно и, возможно, не будет отображаться вообще. Если это не так, добавьте к своему вопросу минимальный полный пример - и, желательно, также прокомментируйте; Я думаю, что получаю автоматическое уведомление о редактировании вопроса, но я не уверен, и лучше перестраховаться, чем сожалеть. - person dave_thompson_085; 30.12.2015
comment
Я понял твою точку зрения, Дэйв. как обсуждалось с моей командой, мы используем rand write hex для генерации 32-байтового ключа. Я являюсь общим len outlen1 + outlen2 в переменной outdata_len, а затем сохраняю '\ 0' в конце исходящих данных как outdata [outdata_len] = '\ 0'. Теперь он получает правильные данные без какого-либо мусора. Я не пробовал с 1-м сценарием, так как нас это устраивает. Спасибо за ваше ценное предложение. Мне это очень помогло. Большое вам спасибо. - person Sai; 05.01.2016