По долгу службы в разработчиках повстречалась задача шифровать текстовые строки алгоритмом 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.
Иточники:
- Статья Владимира Мешкова «Используем средства библиотеки OpenSSL для криптографической защиты данных»
- Пример шифрования файла
- Пример чтения ключа с запросом пароля от автора libeay32.pas
- Создание пары ключей, проверка отпечатка SHA1 и кое что еще
Автор: Lence