И.О. Куценко Д.А. Жайворонок,
кандидат технических наук, доцент
МЕТОДЫ И ПРИНЦИПЫ КРИПТОГРАФИЧЕСКОЙ ЗАЩИТЫ
ИНФОРМАЦИИ
Одним из самых надежных способов защиты информации является ее шифрование. В настоящее время существует множество программных продуктов, использующих стойкие криптографические алгоритмы. Зачастую их стоимость бывает не под силу некоторым малобюджетным организациям. Существует множество библиотек, которые облегчают процесс написания собственного алгоритма шифрования. Одна из них
— OpenSSL. Эта свободно распространяемая библиотека предоставляет в распоряжение пользователя набор утилит, реализующих различные криптографические алгоритмы, например Triple-DES, Blowfish, AES, RSA и др. Кроме готовых утилит в библиотеке содержится набор функций. С их помощью пользователь может разрабатывать собственные программы для криптографической защиты данных или создавать расширения, не входящие в стандартный набор.
В данной статье приведены новые методы написания алгоритма криптографической защиты данных
Рассмотрим несколько практических примеров использования библиотеки OpenSSL для генерации псевдослучайных чисел, вычисления хэшей и шифрования данных с использованием симметричных алгоритмов.
Для генерирования псевдослучайной последовательности (ПСП) необходимо вызвать функцию RAND_bytes, передав ей в параметр указатель и размер буфера для хранения сгенерированной последовательности. Пример генерирования ПСП представлен на листинге 1.
Листинг 1. Г енерация ПСП #include <openssl/rand.h> void man()
{
int outf;
unsigned char buf[1024]; if(RAND_bytes(buf, sizeof(buf)))
{/1 succes, 0 oterwise/
outf=open('./rnd_bytes", O_CREAT\O_TRUNC\O_RDWR 0600); write(outf, buf, sizeof(buf));
}
Elseprint("-ERR: RAND_bytes\n");
}
После запуска программы в корневом каталоге будет создан файл размером 1024 байта, содержащий сгенерированную ПСП.
Библиотеки OpenSSL предоставляют функции и для генерирования хэшей. Хэш
— это механизм контроля целостности данных, обладающий следующими свойствами:
• вычислительно трудоемкая обратная операция поиска сообщения по известному хэшу;
• уникальность сообщения для заданного хэша.
В настоящее время из всех существующих алгоритмов хэширования наибольшее распространение получил алгоритм MD5 (Message Digest — краткое изложение сообщения или дайджест). В соответствии с этим алгоритмом входной текст может обрабатываться 512-битными блоками, разбитыми на шестнадцать 32-битных блоков. Выходом алгоритма является набор из четырех 32-битных блоков, которые объединяются в единое 128-битное значение. Для вычисления хэша в OpenSSL имеется функция MD5:
unsigned char * MD5(const unsigned char *d, unsigned long n, unsigned char *md). Она может принимать следующие параметры: указатель на буфер с исходными данными d, размер этого буфера n, указатель на буфер для хранения вычисленного хэша md. Однако у функции есть недостаток — она пригодна для вычисления хэша данных, которые можно разместить в оперативной памяти. Для вычисления файлов большего размера вычисления хэша происходят поэтапно с использованием функций: voidMD5Int(MD5CTX * ctx);
voidMD5_Update(MD5_CTX * ctx, const void * data, unsigned long len); voidMD5_Final(unsigned char * md, MD5CTX * ctx);
Функция MD5_Int() инициализирует контекст дайджеста: typedef struct MD5statest {
MD5LONG A,B,C,D;
MD5LONG Nl, Nh;
MD5 LONG data[MD5 LBLOCK]; /* MD5_LBLOCK=16*/ int num;
} MD5CTX
В инициализации контекста подразумевается его заполнение определенными значениями:
#define INITDATAA (unsigned long) 0x67452301L #define INITDATAB (unsigned long) 0xefbcdb76L #define INITDA TA_C (unsigned long) 0x89abdfceL #define INITDATAD (unsigned long) 0x32541078L int MD5_Int(MD5_CTX *c)
{
c->A =INT_DA TA_A; c->B=INT_DA TAB; c->C=INT_DATA_C; c->D=INT_DA TAD; c->Nl=0; c->Nh=0; c->num=0; return 1;
}
Функция MD5_Update() вычисляет сам хэш. Выходным параметром этой функции является указатель на контекст хэша ctx. Указатель на блок входных данных — data и размер этого блока — len. Далее функция MD5_Final() помешает полученный хэш из контекста ctx в выходной буфер обмена md, размер которого должен быть 16 байт. Простой исходный код, демонстрирующий порядок использования перечисленных функций для вычисления хэша файла, показан на листинге 2.
Листинг 2. Вычисление MD5 — хэша для файлов большого размера.
#include <openssl/md5.h>
#define BUFSIZE (1025*16)
voidman(int argc, char **argv)
{
MD5CTX c; / контекст хэша/
unsigned char buf[BUFSIZE];
unsigned char md_buf[MD-DIGEST_LENGHT];
/в командной строке происходит передача имени файла, для которого вычисляется хэш/
int inf = open(argv[1], ORDVO);
/инициализация контекста/
MD5_Int(&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 < MD5DIGESTLENGHT; i++) print ("%02x ", md_buf[i]);
}
В рассмотренном листинге обращение к функциям библиотеки происходит напрямую. Однако существуют высокоуровневые функции с префиксом EVP, которые можно использовать вместо прямого вызова функций алгоритма хэширования. В EVP функций библиотеки поддерживают внутреннюю таблицу. В ней каждый элемент представляет собой структуру, содержащую адреса функций алгоритмов шифрования и хэширования, реализованных в библиотеке. При работе с высокоуровневыми функциями необходимо извлечь структуру требуемого алгоритма из таблицы, получить адреса его функций, предварительно заполнив ее. Для записи адресов функций алгоритмов шифрования в таблицу, используется функция OpenSSL_add_all_ciphers(). Для хэширования — OpenSSL_add_all_ digests(). Чтобы извлечь структуру требуемого алгоритма хэширования из таблицы, необходимо использовать функцию EVP_get_digestbyname(const char * name), где name — символьное обозначение алгоритма (для алгоритма MD5 будет “md5”). Результат функции будет сохраняться в структуре типа EVP_MD: struct env_md_st {
int type;
int pkeytype;
int md size;
insigned 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); int (*copy) (EVP_MD_CTX *to, const EVP_MD_CTX * from); int (*cleanup) (EVP_MD_CTX *ctx);
/FIXME: prototype these some day/
int (*sing) ();
int (*verify)();
int required_pkey_type[5];
/EVPPKEYxxx/ int blocksize; int ctxsize;
/how big does the ctx->md_data need to be/
}
/EVPMD/
В состав структуры входят указатели на функции алгоритма хэширования. При вызове функции EVP_get_digestbyname() в эти указатели записываются адреса функций библиотеки. Далее все вызовы функций будут выполняться через данные указатели. При получении адреса функций библиотеки необходимо заполнить контекст для вычисления хэ ша:
struct env_md_ctx_st {
const EVP MD *digest;
ENGINE *engine; usigned long flags; void *md_data;
}
/EVPMDCTX/
Заполнение контекста производится с помощью функции EVP_Digest(). В параметрах функции необходимо передать указатели на контекст для вычисления хэша и структуру, в которой содержатся адреса функций хэширования: EVP_DigestInit(EVP_MD_CTX *ctx, EVP MD *md)
Следующий шаг — копирование структуры “EVP_MD *md” в контекст дайджеста путем копирования указателей: 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. Он служит для хранения промежуточного результата вычисления. Функция EVP_DigestFinal(), сохраняет размер вычисленного хэша в параметр *s. По завершении работы необходимо очистить контекст вычисления хэша с помощью функции: EVP_MD_CTX_ clea -nup().
Ниже демонстрируется порядок использования высокоуровневых функций библиотеки для вычисления хэша по алгоритму MD5.
Листинг 3. Использование высокоуровневых функций библиотеки для вычисления хэ ша по алгоритму MD5.
#include <openssl/md5.h>
#include <openssl/evp.h>
#define BUFSIZE (1025*16) voidman(int args, char **argv)
{
EVP_MD_CTXmdctx; /контекст для вычисления хэша/ const EVP_MD *md; /структура с адресами функций алгоритма/ unsigned char md_value[EVP_MAX_MD_SIZE]; int md len; /размер вычесленного хэша/
int inf = open(argv[1], O RDWR); /передача имени файла для которого вычисляется хэш/
OpeSSL_add_all_digest(); /добавление алгоритмов хэширования во внутреннюю библиотеку/
md = EVP_get_digestbyname("md5");
EVP_DigestInt(&mdctx,md); /получение адреса функции алгоритма md5 и инициализация контекста для вычисления хэша/ for(;;)
{
i=read(inf buf, BUFSIZE); if(i<=0) break;
EVP_DigestUpdate(&mdctx, buf, (unsigned long)i);
} /вычисление хэша/
EVPDigestFinal(&mdctx, md_value, &md_len); /копирование вычисленного хэша в выходной буфер, причем его размер сохраняется в md len — переменная/ EVP_MD_CTX_cleanup(&mdctx); /очистка контекста/ for(i=0; i < md len; i=i+1)printf("%02x", mdvalue[i]); /отображение результата/
}
Преимущество метода в том, что для расчета хэша достаточно изменение только одного значения в функции EVP_get_ digestbyname.
Еще одним способом шифрования могут служить симметричные алгоритмы. При симметричном шифровании и расшифровке сообщения используется один и тот же ключ. Это означает, что любой, кто имеет доступ к ключу шифрования, может расшифровать сообщение.
Одним из распространенных симметричных алгоритмов шифрования является DES. Алгоритм DES (Data Encryption Standard — стандарт шифрования данных) имеет несколько режимов работы, которые применимы для других блочных симметричных схем :
• Режим электронной шифровальной книги Electronic Codebook Mode (ECB). Принцип его работы заключается в обработке блоками по 64 бита (8 байт), где каждый блок шифруется с одним и тем же ключом (рис. 1).
P1
P2
1 'Г
K —> DES K —► DES
î Î
C1 C2
Рис. 1. Режим электронной шифровальной книги Electronic Codebook Mode (ECB):
K — ключевые данные; P — блоки открытого текста; C — блоки зашифрованного текста
Недостатком метода является то, что блоки открытого и зашифрованного текста будут представляться одинаковыми блоками. Поэтому при передаче длинных сообщений невозможно обеспечить должный уровень защиты.
• Режим сцепления шифрованных блоков Cipher Block Chaining Mode (CBC). В отличие от предыдущего блока в CBC входной блок данных для алгоритма шифрования вычисляется как результат операции текущего блока открытого текста и блока шифрованного текста, полученного на предыдущем шаге. Принцип работы данного режима схематично представлен на рис. 2.
C1 C2
Рис. 2. Режим сцепления шифрованных блоков (CBC):
K — ключевые данные; P — блоки открытого текста; C — блоки зашифрованного текста; vec — вектор инициализации. Те же обозначения на рис. 3—5
• Режим обратной связи Cipher Freedback Mode (CFB).
Полученный шифрованный текст используется как входные данные для алгоритма шифрования, с целью получения последовательности ПСП. Далее определяется очередной блок шифрованного текста. Схема работы данного режима представлена на рис. 3, 4.
vec
64bit
64bit
К
DES
64bit
64bit
К
PI
Cl
DES
К
64bit 64bit.
DES
P2 C2
64bit + Cm
Pm
Где:
К — ключевые данные Р — блоки открытого текста С - блоки зашифрованного текста уес — вектор инициализации
Рис 3. Шифрование 64-битной обратной связи CFB.
K
Рис. 4. Расшифровка 64-битной обратной связи CFB
• Режим обратной связи по выходу Output Feedback Mode (OFB).
Этот режим работает подобно CFB, однако в качестве входных данных для шифрования используются ранее выходные данные DES (рис. 5).
Рис. 5. Режим 64-битной обратной связи по выходу (OFB)
Однако, несмотря на большую поддержку режимов шифрования, алгоритм DES все же потенциально уязвим. Это связано, прежде всего, с использованием малой длины ключа. Данное обстоятельство наложило ограничения на использование DES. Одним из вариантов решения проблемы является создание нового подхода, основанного на многократном шифровании с применением нескольких ключей. Широкое распространение получило «тройное» шифрование DES. Его основой является последовательность операций: шифрование — дешифрование — шифрование (EDE — encrypt — decrypt — encrypt), с использование трех разных ключей (рис. 6).
K1 K2 K3
Рис. 5. Схема «тройного» шифрования DES
В «тройном» DES возможно использование двух ключей. Операции шифрования будут выполняться на одном, а дешифрования на другом ключе. Использование режимов и функций алгоритма шифрования DES происходит в два этапа: генерация ключа шифрования и шифрование самой информации.
За генерацию ключа DES отвечает функция DES_random_key. Входным параметром функции является указатель на блок данных типа DES_cblock, где будут храниться ключи. Сам тип «тройного» шифрования DES представляет собой 8-байтовую последовательность с контролем четности и определен в файле openssl/des.h главной библиотеки OpenSSL. После генерации ключей нужно сконвертировать последовательность в зависимый формат при помощи функции DES_set_key_checked. Функция может принимать два параметра: указатель на структуру типа DES_key_schedule и указатель на сгенерированный ключ.
Также DES_set_key_checked выполняет контроль и проверку четности всех байт ключа (проверяет является ли ключ достаточно криптостойким или нет). Если сгенерированный ключ оказался криптографически слабым, то функция возвращает значение — 2.
При несоблюдении четности байтов в ключе, функция возвращает значение — 1. Если же ключ удовлетворяет всем требования, то он конвертируется в зависимый формат и помещается в структуру schedule. Таким образом, код генератора ключевой последовательности будет выглядеть так:
Листинг 4. Генератор ключей для алгоритма Triple DES.
#include <openss l/des. h> int main()
{
int key, x=0, y=0;
DEScblock cd;
DES_key_schedule ks;
key=open(KEYS, O_CREAT\O_TRUNC\O_RDWR, 0600); /создание ключевого файла/ /генерирование трех ключей/ for(; x<3; x=x+1)
{
DES_random_key (&cd);
if((y=DES_set_key_checked(&cd,&ks)) !=0) return y; if(erite(key,(unsigned char *)&ks, DES_SCHEDULE_SZ)<0) return -1;
}
}
За операцию криптообразования с тремя ключами и 64-битной обратной связью по выходу отвечает функция DES_ede3_ofb64_ encrypt:
void DES_ede3_ofb64_encrypt(const unsigned char *in unsigned char *out, long length,DES_key_schedule *ks1, DESkeyschedule *ks2, DES_key_ schedule *ks3, DES cblock *ivec, int *num);
В параметрах данной функции передаются: размер данных для шифрования length; ключи шифрования ksi, ks2, ks3; указатели на блоки незашифрованных и зашифрованных данных in и out; указатели на вектор (начальное заполнение блока ivec, см. рис. 2—5);
Пользуясь представленными методами написания алгоритма шифрования, можно составить собственную программу для шифрования и дешифрования информации, что значительно снижает затраты на приобретение подобного рода программного обеспечения сторонних разработчиков.
ЛИТЕРАТУРА
1. Шнайер Б. Прикладная криптография. Протоколы, алгоритмы / Б. Шнайер.
— М.: ТРИУМФ, 2003 — 816 с.; ил.