RSA шифрование через библиотеку OpenSSL в Delphi

от автора

По долгу службы в разработчиках повстречалась задача шифровать текстовые строки алгоритмом RSA, используя публичный и секретный ключи в PEM формате. При изучении данного вопроса выбор пал на использование библиотеки OpenSSL. Хочу поделиться примерами реализации функционала шифрования на Delphi. Действия выполнялись в ОС Windows, среда разработки Borland Delphi 7.

С чего начать?

В первую очередь необходим пакет openssl, а точнее библиотека libeay32.dll. И для подключения библиотеки нам понадобится готовый юнит libeay32.pas, который необходимо подключить к проекту. В моем примере отсутствует функционал генерации ключей из проекта, как это сделать можно будет почитать в материале по ссылке снизу статьи. Для генерации пары ключей я использовал сам openssl следующими командами из cmd:

openssl genrsa 1024 > private.pem openssl rsa -in private.pem -pubout > public.pem 

Где 1024 является битностью ключа. Отмечу, что от битности ключа зависит и длина строки для шифрования. Если ключ 1024 бита, то зашифровать сможем всего 128 байт, тот самый размер, которому равен размеру ключа. Ниже покажу функцию, определяющие размер структуры RSA ключа. Но это не все. Данный буфер уменьшится на 11 байт, если во время шифрования указать параметр padding, отвечающего за выравнивания данных — PKCS#1.

Сгенерировав ключ в 2048 бит, сможем зашифровать 256 байт. При этом увеличивается размер выходного шифрованного текста, даже если будет зашифрован всего 1 байт.

Для моей задачи было достаточно 117 байт, в которые умещались карточные данные, размер строк в базе, конечно же, значительно вырос. Но того требует безопасность.

Основные функции

Основное, что мы будем использовать для шифрования, — это:

Инициализация крипто функций

OpenSSL_add_all_algorithms; OpenSSL_add_all_ciphers; OpenSSL_add_all_digests; ERR_load_crypto_strings; ERR_load_RSA_strings; 

Destroy

EVP_cleanup;  ERR_free_strings; 

Чтение ключей

//чтение секретного ключа в формате PEM, возвращает структуру RSA //Где bp файл ключа, возвращаемый в RSA структуру указывающей x, cb – адрес функции запрашиваемая пароль к ключу.  function PEM_read_bio_PrivateKey(bp: pBIO; var x: pEVP_PKEY;     cb: TPWCallbackFunction; u: pointer): pEVP_PKEY; cdecl;  //чтение публичного ключа в формате PEM, возвращает структуру RSA function PEM_read_bio_PUBKEY(bp: pBIO; var x: pEVP_PKEY;     cb: TPWCallbackFunction; u: pointer): pEVP_PKEY; cdecl; 

И функции шифрации/дешифрации

//Шифрование/дешифрование flen размера буфера from в буфер _to используя структуру RSA ключа загруженного ранее в режиме выравнивания  //данных padding. Получаем длину шифрованного/дешифрованного буфера или -1 при ошибке //Шифрование публичным ключом function RSA_public_encrypt(flen: integer; from: PCharacter; _to: PCharacter; rsa: pRSA; padding: integer): integer; cdecl;  //Шифрование секретным ключом function RSA_private_encrypt(flen: integer; from: PCharacter; _to: PCharacter; rsa: pRSA; padding: integer): integer; cdecl;  //Дешифрование публичным ключом function RSA_public_decrypt(flen: integer; from: PCharacter; _to: PCharacter; rsa: pRSA; padding: integer): integer; cdecl;  //Дешифрование секретным ключом function RSA_private_decrypt(flen: integer; from: PCharacter; _to: PCharacter; rsa: pRSA; padding: integer): integer; cdecl; 

Приступим к реализации

Итак, мы имеем сгенерированные ключи, dll-ка лежит в папке с проектом, liblea32.pas подключен в uses. Вызываем процедуры инициализации openssl:

procedure LoadSSL; begin   OpenSSL_add_all_algorithms;   OpenSSL_add_all_ciphers;   OpenSSL_add_all_digests;   ERR_load_crypto_strings;   ERR_load_RSA_strings; end;  procedure FreeSSL; begin   EVP_cleanup;   ERR_free_strings; end; 

Пишем функции загрузки ключей.

KeyFile – пусть до ключа (‘C:\key.pem’):

function LoadPublicKey(KeyFile: string) :pEVP_PKEY ; var   mem: pBIO;   k: pEVP_PKEY; begin   k:=nil;   mem := BIO_new(BIO_s_file()); //BIO типа файл   BIO_read_filename(mem, PAnsiChar(KeyFile)); // чтение файла ключа в BIO   try     result := PEM_read_bio_PUBKEY(mem, k, nil, nil); //преобразование BIO  в структуру pEVP_PKEY, третий параметр указан nil, означает для ключа не нужно запрашивать пароль   finally     BIO_free_all(mem);   end; end;  function LoadPrivateKey(KeyFile: string) :pEVP_PKEY; var   mem: pBIO;   k: pEVP_PKEY; begin   k := nil;   mem := BIO_new(BIO_s_file());   BIO_read_filename(mem, PAnsiChar(KeyFile));   try     result := PEM_read_bio_PrivateKey(mem, k, nil, nil);   finally     BIO_free_all(mem);   end; end; 

Вызов функций чтения ключей и обработка ошибок

var FPublicKey: pEVP_PKEY; FPrivateKey: pEVP_PKEY; err: Cardinal; … FPublicKey := LoadPublicKey(‘C:\public.key’);  FPrivateKey := LoadPrivateKey(‘C:\private.key’);    //if FPrivateKey = nil then // если ключ не вернулся, то читаем ошибку if FPublicKey = nil then    begin 	err := ERR_get_error; 	repeat 		log.Lines.Add(string(ERR_error_string(err, nil))); 		err := ERR_get_error; 	until err = 0; 	end; 

Шифрование

var 	rsa: pRSA; // структура RSA 	size: Integer; 	FCryptedBuffer: pointer; // Выходной буфер  	b64, mem: pBIO;  	str, data: AnsiString;  	len, b64len: Integer;  	penc64: PAnsiChar; 	size: Integer; 	err: Cardinal begin 	rsa := EVP_PKEY_get1_RSA(FPrivateKey); // Получение RSA структуры 	EVP_PKEY_free(FPrivateKey); // Освобождение pEVP_PKEY 	size := RSA_size(rsa); // Получение размера ключа 	GetMem(FCryptedBuffer, size); // Определение размера выходящего буфера 	str := AnsiString(‘Some text to encrypt’); // Строка для шифрования  	//Шифрование 	len := RSA_private_encrypt(Length(str),  // Размер строки для шифрования 							  PAnsiChar(str),  // Строка шифрования 							  FCryptedBuffer,  // Выходной буфер 							  rsa, // Структура ключа 							  RSA_PKCS1_PADDING // Определение выравнивания 							  );  	if len > 0 then // длина буфера после шифрования 	  begin 	  // полученный бинарный буфер преобразуем в человекоподобный base64 		b64 := BIO_new(BIO_f_base64); // BIO типа base64 		mem := BIO_push(b64, BIO_new(BIO_s_mem)); // Stream 		try 			BIO_write(mem, FCryptedBuffer, len); // Запись в Stream бинарного выходного буфера 			BIO_flush(mem); 			b64len := BIO_get_mem_data(mem, penc64); //получаем размер строки в base64 			SetLength(data, b64len); // задаем размер выходному буферу 			Move(penc64^, PAnsiChar(data)^, b64len); // Перечитываем в буфер data строку в base64  		finally 			BIO_free_all(mem); 		end; 	  end 	  else 	  begin // читаем ошибку, если длина шифрованной строки -1 		err := ERR_get_error; 		repeat 			log.Lines.Add(string(ERR_error_string(err, nil))); 			err := ERR_get_error; 		until err = 0; 	  end; 	RSA_free(rsa); end; 

Дешифрование

var   rsa: pRSA;   out_: AnsiString;   str, data: PAnsiChar;   len, b64len: Integer;   penc64: PAnsiChar;   b64, mem, bio_out, bio: pBIO;   size: Integer;   err: Cardinal; begin 	//ACryptedData : string; // Строка в base64 	rsa := EVP_PKEY_get1_RSA(FPublicKey); 	size := RSA_size(rsa); 	GetMem(data, size);  // Определяем размер выходному буферу дешифрованной строки 	GetMem(str, size); // Определяем размер шифрованному буферу после конвертации из base64  	//Decode base64 	b64 := BIO_new(BIO_f_base64); 	mem := BIO_new_mem_buf(PAnsiChar(ACryptedData), Length(ACryptedData)); 	BIO_flush(mem); 	mem := BIO_push(b64, mem); 	BIO_read(mem, str , Length(ACryptedData)); // Получаем шифрованную строку в бинарном виде 	BIO_free_all(mem); 	// Дешифрование 	len := RSA_public_decrypt(size, PAnsiChar(str), data, rsa, RSA_PKCS1_PADDING); 	if len > 0 then 	begin	 	// в буфер data данные расшифровываются с «мусором» в конца, поэтому очищаем, поэтому определяем размер переменной out_ и переписываем в нее нужное количество байт из data 		SetLength(out_, len);  		Move(data^, PAnsiChar(out_ )^, len); 	end 	else     begin // читаем ошибку, если длина шифрованной строки -1 		err := ERR_get_error; 		repeat 			log.Lines.Add(string(ERR_error_string(err, nil))); 			err := ERR_get_error; 		until err = 0; 	end; end; 

И заключении пример чтения ключа «зашитого» в приложение

var   mem, keybio: pBIO;   k: pEVP_PKEY;   keystring: AnsiString; begin   keystring :=   '-----BEGIN RSA PRIVATE KEY-----' + #10 +   'MIICXgIBAAKBgQCfydli2u2kJfb2WetkOekjzQIg7bIuU7AzAlBUPuA72UYXWnQ/' + #10 +   'XcdSzEEMWSBLP7FO1vyVXR4Eb0/WqthF0ZViOK5bCN9CnR/1GMMiSqmIdByv/gUe' + #10 +   'Z/UjGrKmxeQOoa2Yt0MJC64cNXgnKmYC7ui3A12LlvNdBBEF3WpcDbv+PQIDAQAB' + #10 +   'AoGBAJnxukKHchSHjxthHmv9byRSyw42c0g20LcUL5g6y4Zdmi29s+moy/R1XOYs' + #10 +   'p/RXdNfkQI0WnWjgZScIij0Z4rSs39uh7eQ5qxK+NH3QIWeR2ZNIno9jAXPn2bkQ' + #10 +   'odS8FPzbZM9wHhpRvKW4FNPXqTc3ZkTcxi4zOwOdlECf9G+BAkEAzsJHgW1Isyac' + #10 +   'I61MDu2qjMUwOdOBYS8GwEBfi/vbn/duwZIBXG/BZ7Pn+cBwImfksEXwx0MTkgF3' + #10 +   'gyaChUSu+QJBAMXX3d94TwcF7lG9zkzc+AR/Onl4Z5UAb1GmUV57oYIFVgW1RIOk' + #10 +   'vqynXWrTjTOg9C9j+VEpBG67LcnkwU16JmUCQH7pukKz9kAhnw43PcycDmhCUgvs' + #10 +   'zCn/V8GCwiOHAZT7qLyhBrzazHj/cZFYknxMEZAyHk3x2n1w8Q9MACoVsuECQQDF' + #10 +   'U7cyara31IyM7vlS5JpjMdrKyPLXRKXDFFXYHQtLubLA4rlBbBHZ9txP7kzJj+G9' + #10 +   'WsOS1YxcPUlAM28xrYGZAkEArVKJHX4dF8UUtfvyv78muXJZNXTwmaaFy02xjtR5' + #10 +   'uXWT1QjVN2a6jv6AW7ukXiSoE/spgfvdoriMk2JSs88nUw==' + #10 +   '-----END RSA PRIVATE KEY-----' ;   k := nil;     keybio := BIO_new_mem_buf(Pchar(keystring), -1);   mem := BIO_new(BIO_s_mem());   BIO_read(mem, PAnsiChar(keystring), length(PAnsiChar(keystring)));   try     result := PEM_read_bio_PrivateKey(keybio, k, nil, nil);   finally     BIO_free_all(mem);   end; end; 

На этом мои примеры реализации заканчиваются. Исходники проекта доступны на Github.

Иточники:

ссылка на оригинал статьи https://habrahabr.ru/post/280302/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *