Загрузил Евгений Черкашин

Используем средства библиотеки OpenSSL

реклама
безопасность
Используем средства библиотеки 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 с.; ил.
Скачать