безопасность Используем средства библиотеки OpenSSL для криптографической защиты данных Владимир Мешков Самым надежным способом скрыть информацию от посторонних глаз является ее шифрование. В настоящее время существует большое количество стойких криптографических алгоритмов, позволяющих надежно защитить конфиденциальные данные и множество их программных реализаций, доступных для свободного использования. С егодня мы поговорим о библиотеке OpenSSL. Эта сво- раметрах указатель на буфер для хранения сгенерированбодно распространяемая библиотека предоставляет ной последовательности и размер этого буфера. Следуюв распоряжение пользователя набор утилит, реали- щий код демонстрирует это: зующих различные криптографические алгоритмы, такие Листинг 1. Генерация ПСП как Triple-DES, Blowfish, AES, RSA и другие. Порядок использования утилит был подробно опи#include <openssl/rand.h> сан Всеволодом Стаховым в статье «Теория и практика void main() OpenSSL» [1]. { int outf; Кроме готовых к применению утилит, библиотека соunsigned char buf[1024]; держит набор функций, с помощью которых пользователь if(RAND_bytes(buf, sizeof(buf))) { /* 1 succes, ↵ может разрабатывать собственные программы для крип0 otherwise */ тографической защиты данных или создавать расширеoutf = open("./rnd_bytes", O_CREAT|O_TRUNC|O_RDWR, ↵ 0600); ния, не входящие в стандартный набор. Большинство этих write(outf, buf, sizeof(buf)); функций достаточно хорошо документированы, и наличие } else printf("-ERR: RAND_bytes\n"); } примеров программ значительно облегчает задачу изучения порядка их использования. Сохраним этот код в файле rand_test.c и получим исполНе углубляясь в детали реализации алгоритмов, рассмотрим несколько практических примеров использова- няемый файл формата ELF при помощи команды: ния библиотеки OpenSSL для генерации псевдослучайgcc –o rand_test rand_test.c -lssl ных чисел, вычисления хэшей и шифрования данных с использованием симметричных и асимметричных алгоритмов. Основное внимание акцентируем на вопросах, котоПосле запуска на выполнение файла rand_test в терые, на мой взгляд, недостаточно полно освещены в офи- кущем каталоге будет создан файл rnd_bytes размером циальной документации и требуют более детальной про- 1024 байта, содержащий сгенерированную ПСП. работки. Далее по тексту библиотека OpenSSL будет имено- Вычисление хэшей ваться просто библиотека, кроме специально оговорен- Хэш – это механизм контроля целостности данных, обланых случаев. дающих специальными свойствами: зная сообщение, легко вычислить хэш. Обратная задача нахождения сообщения по известному хэшу являетГенерация псевдослучайной последовательности ся вычислительно-трудоемкой; Сгенерировать псевдослучайную последовательность для заданного сообщения хэш является уникальным, (ПСП) при помощи библиотеки очень просто. Для этого дот.е. не должно существовать двух разных сообщений с одинаковыми хэшами. статочно вызвать функцию RAND_bytes, передав ей в па- 48 безопасность Листинг 2 демонстрирует порядок использования переИз всех существующих алгоритмов хэширования наибольшее распространение получил алгоритм MD5. Аббре- численных выше функций для вычисления хэша файла: виатура «MD» означает Message Digest (краткое изложение Листинг 2. Вычисление MD5-хэша для файла большого размера сообщения, или дайджест). В соответствии с этим алгоритмом, входной текст обрабатывается 512-битовыми блоками, #include <openssl/md5.h> разбитыми на шестнадцать 32-битовых подблоков. Выхо#define BUFSIZE (1025*16) дом алгоритма является набор из четырех 32-битовых блоvoid main(int argc, char **argv) ков, которые объединяются в единое 128-битное значение. { Рассмотрим, как вычисляется хэш сообщения по алгоритMD5_CTX c; /* контекст хэша */ unsigned char buf[BUFSIZE]; му MD5 с использованием средств библиотеки. unsigned char md_buf[MD5_DIGEST_LENGTH]; Для вычисления хэша библиотека предоставляет в на/* В командной строке передается имя файла, ше распоряжение функцию MD5: unsigned char * MD5(const unsigned char *d, ↵ unsigned long n, unsigned char *md) Эта функция принимает три параметра – указатель на буфер с исходными данными d, размер этого буфера n и указатель на буфер для хранения вычисленного хэша md. Это очень простая в использовании функция, но у нее есть недостаток – она пригодна для вычисления хэша данных, которые можно полностью разместить в оперативной памяти. Для файлов большого размера вычисление хэша производится поэтапно. Для этого библиотека предоставляет следующий набор функций (см. openssl/md5.h): void MD5_Init(MD5_CTX * ctx); void MD5_Update(MD5_CTX * ctx, const void * data, ↵ unsigned long len); void MD5_Final(unsigned char * md, MD5_CTX * ctx); для которого вычисляется хэш */ int inf = open(argv[1], O_RDWR); /* Инициализируем контекст */ MD5_Init(&c); /* Вычисляем хэш */ for(;;) { int i = read(inf, buf, BUFSIZE); if(i <= 0) break; MD5_Update(&c, buf, (unsigned long)i); } /* Помещаем вычисленный хэш в буфер md_buf */ MD5_Final(md_buf, &c); /* Отображаем результат */ for(i = 0; i < MD5_DIGEST_LENGTH; i++) ↵ printf("%02x", md_buf[i]); } Проверить правильность вычисления хэша можно при помощи утилиты md5sum. В рассмотренном листинге мы обращаемся к функциФункция MD5_Init() инициализирует контекст дайджес- ям библиотеки напрямую. В документации рекомендуется использовать высокоуровневые функции с префиксом EVP та – структуру, определенную в файле openssl/md5.h: вместо прямого вызова функций алгоритма хэширования. typedef struct MD5state_st { Разберемся подробнее, что это за EVP-функции. MD5_LONG A,B,C,D; Библиотека поддерживает внутреннюю таблицу, в которой MD5_LONG Nl,Nh; MD5_LONG data[MD5_LBLOCK]; /* MD5_LBLOCK = 16 */ каждый элемент представляет собой структуру, содержащую int num; адреса функций алгоритмов шифрования и хэширования, } MD5_CTX; реализованных в библиотеке. Для работы с высокоуровнеИнициализация контекста подразумевает его заполне- выми функциями необходимо извлечь структуру требуемого ние определенными значениями (см. файл md5_dgst.c ис- алгоритма из этой таблицы, получив, таким образом, адреса его функций. Но прежде эту таблицу необходимо заполнить. ходных текстов библиотеки): Адреса функций алгоритмов хэширования записываются #define INIT_DATA_A (unsigned long)0x67452301L в таблицу при помощи функции OpenSSL_add_all_digests(), #define INIT_DATA_B (unsigned long)0xefcdab89L адреса функций алгоритмов шифрования – при помощи #define INIT_DATA_C (unsigned long)0x98badcfeL #define INIT_DATA_D (unsigned long)0x10325476L функции OpenSSL_add_all_ciphers(). Извлечь структуру требуемого алгоритма хэшироint MD5_Init(MD5_CTX *c) { вания из таблицы можно при помощи функции EVP_get_ c->A=INIT_DATA_A; digestbyname(const char * name), где name является символьc->B=INIT_DATA_B; c->C=INIT_DATA_C; ным обозначением алгоритма. Для алгоритма MD5 это будет c->D=INIT_DATA_D; «md5». Список всех имен приведен в файле openssl/object.h. c->Nl=0; c->Nh=0; Результат работы функции EVP_get_digestbyname() сохраc->num=0; няется в структуре типа EVP_MD: return 1; } Функция MD5_Update() вычисляет хэш. Входными параметрами этой функции являются указатель на контекст хэша ctx, указатель на блок входных данных data и размер этого блока len. Функция MD5_Final() помещает вычисленный хэш из контекста ctx в выходной буфер md, размер которого должен быть равен 16 байт. №4, апрель 2006 struct env_md_st { int type; int pkey_type; int md_size; unsigned long flags; int (*init)(EVP_MD_CTX *ctx); int (*update)(EVP_MD_CTX *ctx,const void *data, ↵ unsigned long count); int (*final)(EVP_MD_CTX *ctx,unsigned char *md); 49 безопасность int (*copy)(EVP_MD_CTX *to,const EVP_MD_CTX *from); int (*cleanup)(EVP_MD_CTX *ctx); Листинг 3. Вычисление MD5-хэша файла с использованием высокоуровневых функций библиотеки /* FIXME: prototype these some day */ int (*sign)(); int (*verify)(); int required_pkey_type[5]; /*EVP_PKEY_xxx */ int block_size; int ctx_size; /* how big does the ctx->md_data need to be */ } /* EVP_MD */; #include <openssl/md5.h> #include <openssl/evp.h> Эта структура определена в файле openssl/evp.h. В ее состав входят указатели на функции алгоритма хэширования. При вызове функции EVP_get_digestbyname() в эти указатели будут записаны реальные адреса функций библиотеки для работы с выбранным типом алгоритма хэширования, и в дальнейшем все вызовы функций будут выполняться косвенно через эти указатели. Получив адреса библиотечных функций, необходимо заполнить контекст для вычисления хэша – структуру типа EVP_MD_CTX (см. openssl/evp.h): struct env_md_ctx_st { const EVP_MD *digest; ENGINE *engine; unsigned long flags; void *md_data; } /* EVP_MD_CTX */; Заполнение контекста выполняется при помощи функции EVP_DigestInit(). В параметрах этой функции передаются указатели на контекст для вычисления хэша и на структуру, содержащую адреса функций алгоритма хэширования: EVP_DigestInit(EVP_MD_CTX * ctx, EVP_MD * md) Функция копирует структуру «EVP_MD * md» в контекст дайджеста путем приравнивания соответствующих указателей (см. файл crypto/evp/digest.c исходных текстов библиотеки): ctx->digest = md; Заполнив контекст, мы получаем возможность вызывать библиотечные функции для вычисления хэша, используя адреса, которые сохранены в структуре digest-контекста. Вычисление хэша выполняет функция EVP_DigestUpdate(), функция EVP_DigestFinal() копирует вычисленный хэш из контекста дайджеста в выходной буфер: int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, ↵ unsigned int cnt); int EVP_DigestFinal(EVP_MD_CTX *ctx, unsigned char *md, ↵ unsigned int *s); Параметры функции EVP_DigestUpdate – указатель на контекст для вычисления хэша ctx, буфер d для хранения промежуточного результата вычисления и размер этого буфера cnt. Функция EVP_DigestFinal сохраняет размер вычисленного хэша в последнем параметре *s. По завершении работы контекст для вычисления хэша очищается при помощи функции EVP_MD_CTX_cleanup(). Листинг 3 демонстрирует порядок использования высокоуровневых функций библиотеки для вычисления хэша файла по алгоритму MD5. 50 #define BUFSIZE (1025*16) void main(int argc, char **argv) { EVP_MD_CTX mdctx; /* контекст для вычисления хэша */ const EVP_MD * md; /* структура с адресами функций алгоритма */ unsigned char md_value[EVP_MAX_MD_SIZE]; int md_len; /* размер вычисленного хэша */ /* В командной строке передаем имя файла, для которого вычисляется хэш */ int inf = open(argv[1], O_RDWR); /* Добавляем алгоритмы хэширования во внутреннюю таблицу библиотеки */ OpenSSL_add_all_digests(); /* Получаем адреса функций алгоритма MD5 и инициализируем контекст для вычисления хэша */ md = EVP_get_digestbyname("md5"); EVP_DigestInit(&mdctx, md); /* Вычисляем хэш */ for(;;) { i = read(inf, buf, BUFSIZE); if(i <= 0) break; EVP_DigestUpdate(&mdctx, buf, (unsigned long)i); } /* Копируем вычисленный хэш в выходной буфер. Размер хэша сохраняем в переменной md_len */ EVP_DigestFinal(&mdctx, md_value, &md_len); /* Очищаем контекст */ EVP_MD_CTX_cleanup(&mdctx); /* Отобразим результат */ for(i = 0; i < md_len; i++) printf("%02x", ↵ md_value[i]); } Универсальность метода использования функций высокого уровня очевидна – для расчета хэша по новому алгоритму достаточно изменить только его название в функции EVP_get_digestbyname. Поэтому именно этот метод рекомендуется к использованию разработчиками библиотеки. Симметричные алгоритмы шифрования Целью шифрования информации является предотвращение угрозы нарушения ее конфиденциальности, т.е. несанкционированное ознакомление с ней. Алгоритмы шифрования можно разделить на две основные категории: симметричное шифрование; ассиметричное шифрование. В алгоритмах симметричного шифрования используется один и тот же ключ для шифрования и расшифровки сообщения. Это означает, что любой, кто имеет доступ к ключу шифрования, может расшифровать сообщение. Алгоритмы симметричного шифрования именно поэтому и называют алгоритмами с секретным ключом – ключ шифрования должен быть доступен только тем, кому предназначено сообщение. Симметричное шифрование идеально подходит для шифрования информации «для себя», например, с целью отсечь несанкционированный доступ к ней в отсутствии владельца. Библиотека поддерживает большое количество симметричных алгоритмов. Некоторые из них мы сейчас рассмотрим, и начнем с самого знаменитого – с DES. безопасность Алгоритм DES Алгоритм DES (Data Encryption Standart, стандарт шифрования данных) был разработан в 1973 году компанией IBM и долгое время являлся основным стандартом шифрования в мире. Этот алгоритм использует 56-битный ключ и шифрует данные блоками по 64 бита. Имеет несколько режимов работы, которые применимы и для других блочных шифров симметричной схемы: Режим электронной шифровальной книги Electronic Codebook Mode (ECB). Простейший режим. Открытый текст обрабатывается блоками по 64 бита (8 байт) и каждый блок шифруется с одним и тем же ключом (см. рис. 1). Самой важной особенностью режима ECB является то, что одинаковые блоки открытого текста в шифрованном тексте будут также представляться одинаковыми блоками. Поэтому при передаче достаточно длинных сообщений режим ECB не может обеспечить необходимый уровень защиты. Если сообщение имеет явно выраженную структуру, у криптоаналитика появляется возможность использовать регулярности текста. Например, если известно, что в начале сообщения всегда размещается определенный заголовок, криптоаналитик может получить в свое распоряжение целый набор соответствующих пар блоков открытого и шифрованного текста. Режим сцепления шифрованных блоков Cipher Block Chaining Mode (CBC). Эта технология свободна от недостатков режима ECB. В режиме CBC входной блок данных для алгоритма шифрования вычисляется как результат операции XOR текущего блока открытого текста и блока шифрованного текста, полученного на предыдущем шаге (см. рис. 2). Режим шифрованной обратной связи Cipher Feedback Mode (CFB). Полученный на предыдущем шаге шифрованный текст используется как входные данные для алгоритма шифрования с целью получения псевдослучайной последовательности (ПСП), XOR-разница которой и блока открытого текста определяет очередной блок шифрованного текста (см. рис. 3) №4, апрель 2006 Режим обратной связи по выходу Output Feedback Mode (OFB). Работает подобно CFB, но в качестве входных данных для алгоритма шифрования используются ранее полученные выходные данные DES (см. рис. 4). Если проводить аналогии с алгоритмом ГОСТ 28147-89, то режим ECB соответствует режиму простой замены, OFB – режиму гаммирования, CFB – режиму гаммирования с обратной связью. Малая длина ключа и постоянно растущая мощность современных вычислительных комплексов сделали алгоритм DES потенциально уязвимым перед атакой, основанной на полном переборе ключей. Это обстоятельство наложило существенные ограничения на использование DES в чистом виде и потребовало поиска альтернативы данному шифру. Один из вариантов решения проблемы предполагал создание совершенно нового алгоритма, другой подход сделал ставку на многократное шифрование с помощью DES с применением нескольких ключей. Широкое распространение получил «тройной» DES (Triple-DES), представляющий собой последовательность операций шифрования-дешифрования-шифрования (EDE – encryptdecrypt-encrypt) с использованием трех разных ключей. Схема «тройного» DES представлена на рис. 5. «Тройной» DES может также использовать два ключа. В этом случае операции шифрования выполняются на одном ключе, а операция дешифрования – на другом. Использование функций библиотеки, реализующих алгоритм DES, предполагает два этапа: генерация ключей и собственно шифрование информации. Генерацию DES-ключа выполняет функция DES_random_key(DES_ cblock *ret). Входным параметром функции является указатель на блок данных типа DES_cblock, в котором будет сохранен ключ. Тип DES_cblock определен в файле openssl/des.h, и представляет собой 8-байтовую последовательность с контролем четности. Младший значащий бит каждого байта является битом четности: Рисунок 1. Режим электронной шифровальной книги ECB Рисунок 2. Режим сцепления шифрованных блоков CBC Рисунок 3. Режим 64-битовой шифрованной обратной связи CFB Рисунок 4. Режим 64-битовой обратной связи по выходу OFB Рисунок 5. Схема «тройного» DES 51 безопасность typedef unsigned char DES_cblock[8]; вертого блока используются только 2 байта. Так вот значение num показывает, сколько байт последнего 8-байтоПосле генерации ключ необходимо сконвертиро- вого блока мы используем, или, как сказано в документавать в платформенно-зависимый формат при помощи ции, «how much of the 64bit block we have used is contained функции DES_set_key_checked(const_DES_cblock *key, in *num» (см. комментарии к функции DES_ede3_ofb64_ DES_key_schedule *schedule). Функция принимает два па- encrypt, файл crypto/des/ofb64ede.c исходных текстов бибраметра – указатель на сгенерированный ключ и указа- лиотеки). тель на структуру типа DES_key_schedule. Этот струкЛистинг 5. Фрагмент программы для криптопреобразования турный тип определен в файле openssl/des.h. Функция файла по алгоритму Triple-DES с использованием трех ключей DES_set_key_checked выполняет контроль четности всех и 64-битной обратной связи по выходу байт ключа и проверяет, можно ли использовать его для /* Буферы для входных и выходных (зашифрованных) данных */ шифрования, т.е. является ключ криптографически сильunsigned char inbuf[1024], outbuf[1024]; ным или нет. Если четность байт не соблюдается, функция /* Структура для хранения ключевых данных */ возвращает -1. Если сгенерированный ключ оказался крипDES_key_schedule ks1, ks2, ks3; тографически слабым (weak), функция возвращает -2. Ес/* Считываем три ранее созданных ключа ли ключ удовлетворяет всем требованиям, то он конверти(key – дескриптор ключевого файла) */ read(key,(unsigned char *)&ks1, DES_SCHEDULE_SZ); руется в платформенно-зависимый формат и помещается read(key,(unsigned char *)&ks2, DES_SCHEDULE_SZ); в структуру schedule. read(key,(unsigned char *)&ks3, DES_SCHEDULE_SZ); Исходя из вышеизложенного, код генератора ключевой /* Открываем входной и создаем выходной файлы */ последовательности для алгоритма Triple-DES будет выгля. . . . деть следующим образом: Листинг 4. Генератор ключей для алгоритма Triple-DES #include <openssl/des.h> int main() { int key, i = 0, j = 0; DES_cblock cb; DES_key_schedule ks; /* Создаем ключевой файл */ key = open(KEYS, O_CREAT|O_TRUNC|O_RDWR, 0600); /* Генерируем три ключа */ for(; i < 3; i++) { DES_random_key(&cb); if((j = DES_set_key_checked(&cb, &ks)) != 0) return j; if(write(key, (unsigned char *)&ks, ↵ DES_SCHEDULE_SZ) < 0) return -1; } } Операцию криптопреобразования по алгоритму Triple-DES с тремя к лючами и 64-битной обратной связью по выходу (режим OFB) выполняет функция DES_ede3_ofb64_encrypt: void DES_ede3_ofb64_encrypt(const unsigned char *in, ↵ unsigned char *out, long length, ↵ DES_key_schedule *ks1, DES_key_schedule *ks2, ↵ DES_key_schedule *ks3, DES_cblock *ivec, int *num); В параметрах функции передаются: указатели на блоки незашифрованных и зашифрованных данных in и out; размер данных для шифрования length; ключи шифрования ks1, ks2, ks3; указатель на вектор инициализации ivec (начальное заполнение блока ivec, см. рис. 2-4); Разъясним назначение последнего параметра int *num. Шифрование выполняется блоками по 64 бита, но длина входного сообщения (файла) не обязательно должна быть кратна этому значению. Например, файл размером 26 байт состоит из трех целых 8-байтовых блоков, а из чет- 52 /* Шифруем файл */ for(;;) { inlen = fread(inbuf, 1, 1024, in); if(inlen <= 0) break; DES_ede3_ofb64_encrypt(inbuf, outbuf, (long)inlen, ↵ &ks1, &ks2, &ks3, (DES_cblock *)ivec, &num); } fwrite(outbuf, 1, inlen, out); С перечнем всех функций, реализующих различные режимы алгоритма DES, можно ознакомиться на страницах руководства man des, описание режимов приведено в man des_modes. Алгоритм Blowfish Blowfish – симметричный блочный шифр, разработанный Брюсом Шнайером (Bruce Schneier). Этот шифр использует ключи разной длины, обычно 128 бит, и шифрует данные блоками по 64 бита. Алгоритм поддерживает такие же режимы, как и DES (см. раздел «Алгоритм DES»), и считается одним из самых быстрых в своем классе. Как и в случае алгоритма DES, использование Blowfish включает две стадии: генерацию ключевых данных и собственно криптопреобразование информации. Генерация ключевых данных предполагает получение N-битной случайной последовательности и последующее ее преобразование при помощи функции BF_set_key(), прототип которой определен в файле openssl/blowfish.h: void BF_set_key(BF_KEY *key, int len, ↵ const unsigned char *data); Задача этой функции – поместить ключ длиной len из буфера data в структуру key. Криптопреобразование данных в режиме CFB c 64-битной обратной связью выполняет функция BF_cfb64_encrypt: void BF_cfb64_encrypt(const unsigned char *in, ↵ unsigned char *out, long length, ↵ const BF_KEY *schedule, unsigned char *ivec, ↵ int *num, int enc); безопасность fwrite(outbuf, 1, inlen, out); Думаю, что после того как мы рассмотрели DES, ни} кому не составит труда разобраться в назначении пара} метров этой функции. Остановимся только на последнем параметре – int enc. Он задает режим работы алгоритма и может принимать два значения: BF_ENCRYPT для ре- Использование высокоуровневых функций жима шифрования и BF_DECRYPT для режима дешиф- библиотеки для шифрования рования. Оба эти значение определены в файле openssl/ Как и в случае алгоритмов хэширования (см. раздел «Выblowfish.h: числение хэшей»), разработчики библиотеки рекомендуют использовать функции высокого уровня вместо прямо#define BF_ENCRYPT 1 го обращения к функциям алгоритма шифрования. Поря#define BF_DECRYPT 0 док использования высокоуровневых функций шифроваА теперь оформим все теоретические выкладки в виде ния и хэширования идентичен – в обоих случаях необходифункции криптографического преобразования информа- мо создать и инициализировать контекст, записав в него ции по алгоритму Blowfish, работающему в режиме 64-бит- адреса функций необходимых алгоритмов. Адреса выбиной обратной связи (CFB-64). раются из внутренней таблицы, куда они попадают при помощи функции OpenSSL_add_all_ciphers(). Листинг 6. Функция криптографического преобразования Контекст алгоритма шифрования представляет собой информации по алгоритму Blowfish, режим 64-битной обратной структуру типа EVP_CIPHER_CTX, определенную в файсвязи (CFB-64) ле openssl/evp.h. Первым элементом этой структуры яв#include <openssl/blowfish.h> ляется указатель на структурный тип EVP_CIPHER следу#define BUFSIZE 1024 ющего вида: /* Функция криптопреобразования информации. Параметры функции: дескрипторы входного и выходного файлов и режим работы – BF_ENCRYPT для шифрования и BF_DECRYPT для дешифрования */ int do_crypt(FILE *in, FILE *out, int mode) { int num = 0; unsigned char inbuf[BUFSIZE], outbuf[BUFSIZE]; /* Ключ шифрования длиной 128 бит и вектор инициализации */ const unsigned char key[16]; unsigned char iv[8]; BF_KEY bfkey; /* Помещаем ключ в структуру bfkey */ BF_set_key(&bfkey, sizeof(key), key); /* Шифруем блоки входного файла */ for(;;) { int inlen = fread(inbuf, 1, BUFSIZE, in); if(inlen <= 0) break; BF_cfb64_encrypt(inbuf, outbuf, (long)inlen, ↵ &bfkey, iv, &num, mode); } fwrite(outbuf, 1, inlen, out); } return 1; Следующий фрагмент функции иллюстрирует порядок криптопреобразования по алгоритму Blowfish, режим 64-битной обратной связи по выходу (OFB-64). Листинг 7. фрагмент функции криптографического преобразования информации по алгоритму Blowfish, режим 64-битной обратной связи по выходу (OFB-64) void do_crypt(FILE *in, FILE *out) { unsigned char inbuf[BUFSIZE]; unsigned char outbuf[BUFSIZE]; /* Ключ и вектор инициализации */ . . . BF_set_key(&bfkey, KEY_SIZE, key); for(;;) { int inlen = fread(inbuf, 1, BUFSIZE, in); if(inlen <= 0) break; BF_ofb64_encrypt(inbuf, outbuf, (long)inlen, ↵ &bfkey, iv, &num); №4, апрель 2006 struct evp_cipher_st { int nid; int block_size; int key_len; /* Default value for variable length ciphers */ int iv_len; unsigned long flags; /* Various flags */ int (*init)(EVP_CIPHER_CTX *ctx, ↵ const unsigned char *key, const unsigned char *iv, ↵ int enc); /* init key */ int (*do_cipher)(EVP_CIPHER_CTX *ctx, ↵ unsigned char *out, const unsigned char *in, ↵ unsigned int inl);/* encrypt/decrypt data */ int (*cleanup)(EVP_CIPHER_CTX *); /* cleanup ctx */ int ctx_size; /* how big ctx->cipher_data needs to be */ int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr); void *app_data; /* Application data */ } /* EVP_CIPHER */; Этот структурный тип содержит в своем составе указатели на функции, которые заполняются необходимыми нам значениями – адресами функций соответствующих алгоритмов. Получить адреса этих функций можно двумя способами. Первый способ подразумевает поиск по символьному имени алгоритма: OpenSSL_add_all_ciphers(); const EVP_CIPHER * cipher = EVP_get_cipherbyname("des_cbc"); Функция EVP_get_cipherbyname(const char * name) извлекает адреса функций алгоритма шифрования из внутренней таблицы библиотеки и заполняет структуру cipher. Входные параметры функции – символьное имя алгоритма, в данном случае это DES, режим CBC. Перечень всех имен содержится в файле openssl/object.h. Второй способ – прямое обращение к нужной EVPфункции: const EVP_CIPHER *cipher = EVP_des_cbc(); Получив адреса функций, инициализируем контекст алгоритма: 53 безопасность EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *ctx); EVP_EncryptInit(EVP_CIPHER_CTX *ctx, ↵ const EVP_CIPHER *cipher, const unsigned char *key, ↵ const unsigned char *iv); Функция EVP_CIPHER_CTX_init обнуляет структуру, выделенную под контекст. Инициализацию контекста выполняет функция EVP_EncryptInit(). Параметры функции – указатель на контекст алгоритма ctx, структура с адресами библиотечных функций алгоритма cipher, блок с ключевыми данными key и вектор инициализации iv. После инициализации контекста можно прист упать к операции шифрования. Делает это функция EVP_EncryptUpdate(): Если мы захотим использовать другой алгоритм, нам достаточно будет заменить одну строку в исходном тексте. Например, для использования алгоритма Blowfish в режиме шифрованной обратной связи по выходу (OFB) необходимо заменить строку: cipher = EVP_aes_256_cfb(); строкой: cipher = EVP_bf_ofb(); а также задать правильную длину ключевых данных. Список всех EVP-функций находится в файле openssl/ int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, ↵ evp.h. unsigned char *out, int *outl, ↵ Обратный процесс дешифрования информации отличаconst unsigned char *in, int inl); ется только названиями функций: вместо «EVP_EnryptInit» Функция шифрует inl байтов из буфера in и записывает пишем «EVP_DecryptInit», вместо «EVP_EncryptUpdate» – зашифрованные данные в буфер out. В переменной outl со- «EVP_DecryptUpdate» и т. д. Фрагмент функции дешифровахраняется количество зашифрованных байтов. ния файла, зашифрованного по алгоритму AES с 256-битЕсли размер сообщения не кратен размеру блока шиф- ным ключом в режиме 64-битовой обратной связи, предрования, то выполняется вызов функции EVP_EncryptFinal, ставлен в листинге 9. которая «дошифровывает» оставшиеся данные: int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, ↵ unsigned char *out, int *outl); Следующий код демонстрирует использование функций высокого уровня для шифрования файла по алгоритму AES с ключом длиной 256 бит, режим 64-битовой шифрованной обратной связи CFB. Листинг 9. Дешифрование файла, зашифрованного по алгоритму AES, длина ключа 256 бит, режим 64-битовой шифрованной ОС int do_decrypt(char *infile) { /* Объявляем переменные */ . . . . /* Обнуляем контекст и выбираем алгоритм дешифрования */ EVP_CIPHER_CTX_init(&ctx); EVP_DecryptInit(&ctx, EVP_aes_256_cfb(), key, iv); Листинг 8. Шифрование файла по алгоритму AES, длина ключа 256 бит, режим 64-битовой шифрованной обратной связи /* Открываем входной и создаем выходной файлы */ . . . . #include <openssl/evp.h> /* Дешифруем данные */ for(;;) { inlen = fread(inbuf, 1, BUFSIZE, in); if(inlen <= 0) break; #define BUFSIZE 1024 int do_crypt(char *infile) { int outlen, inlen; FILE *in, *out; unsigned char key[32]; /* 256-битный ключ */ unsigned char iv[8]; /* вектор инициализации */ unsigned char inbuf[BUFSIZE], outbuf[BUFSIZE]; EVP_CIPHER_CTX ctx; const EVP_CIPHER * cipher; /* Обнуляем структуру контекста */ EVP_CIPHER_CTX_init(&ctx); /* Выбираем алгоритм шифрования */ cipher = EVP_aes_256_cfb(); /* Инициализируем контекст алгоритма */ EVP_EncryptInit(&ctx, cipher, key, iv); /* Шифруем данные */ for(;;) { inlen = fread(inbuf, 1, BUFSIZE, in); if(inlen <= 0) break; if(!EVP_EncryptUpdate(&ctx, outbuf, &outlen, ↵ inbuf, inlen)) return 0; fwrite(outbuf, 1, outlen, out); } } 54 if(!EVP_EncryptFinal(&ctx, outbuf, &outlen)) return 0; fwrite(outbuf, 1, outlen, out); EVP_CIPHER_CTX_cleanup(&ctx); return 1; } if(!EVP_DecryptUpdate(&ctx, outbuf, &outlen, ↵ inbuf, inlen)) return 0; fwrite(outbuf, 1, outlen, out); /* Завершаем процесс дешифрования */ if(!EVP_DecryptFinal(&ctx, outbuf, &outlen)) return 0; } . . . . Работоспособность всех программ была проверена для ОС Linux Slackware 10.2, компилятор gcc-3.3.6, библиотека OpenSSL 0.9.7c. Исходные тексты всех программ, рассмотренных в данной статье, вы можете скачать с сайта http:// bob.netport.com.ua/ssl.tar.gz. Во второй части статьи мы рассмотрим примеры использования функций библиотеки для криптографической защиты данных с помощью ассиметричных алгоритмов. Литература: 1. Стахов В. Теория и практика OpenSSL. – Журнал «Системный администратор», №1(2), январь 2003 г. – 17-26 с. 2. Шнайер Б. Прикладная криптография. Протоколы, алгоритмы, исходные тексты на языке Си. – М.: Издательство ТРИУМФ, 2003 – 816 с.; ил.