По долгу службы в разработчиках повстречалась задача шифровать текстовые строки алгоритмом 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_public_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_private_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;
И заключении пример чтения ключа «зашитого» в приложение
В примере указан приватный ключ и его чтение, с таким же успехом «зашивается» и читается публичный ключ функцией PEM_read_bio_PUBKEY
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 и кое что еще
UPD
Из дискуссий в комментариях внесу дополнения: если мы зашифровываем нужную нам строку публичным ключом, то расшифровывается она только секретным и наоборот — если секретным, то расшифровать можно только публичным ключом. В моем случае у клиента публичный ключ, которым он шифрует данные и только на сервере их можно расшифровать секретным ключом.
Комментарии (27)
Kolyuchkin
28.03.2016 11:37-4Уважаемый автор, у меня к Вам есть один главный вопрос и несколько второстепенных. Главный: «Зачем Вы используете криптографию, не разобравшись что к чему?»
Второстепенные: 1) «Если Вы пишете под Windows да еще используете „буржуйские“ криптоалгоритмы, то не проще ли использовать для этого нативный для Windows крипто-сервис-провайдер? Он „искаропки“ присутствует.»
2) «Если Вы заботитесь о безопасности, то почему Вам разрешили на территории РФ (из слов „по долгу службы“ я понял, что это работа под заказчика, не Ваша инициатива… хотя, „шифровать текстовые строки алгоритмом RSA“ отдают амбре профанации либо Вас, либо Вашего заказчика) использовать „буржуйские“ криптоалгоритмы? На этот вопрос можете не отвечать, если Вы разрабатывали курсовик или лабораторку для кого-то.»Lence
28.03.2016 23:35Разобравшись. В статье есть дополнения. Проще мне было реализовать на openssl. По Windows крипто провайдеру тоже есть прекрасная статья с примерами реализации на Delphi. Но речь не об выборе. OpenSSL open source, а Windows крипто провайдер куда сольет данные?
Я не из РФ, кто Вам в РФ запрещает? Законы? Религия?Kolyuchkin
29.03.2016 09:24Видимо, не до конца разобравшись, все-таки. Разъясняю, вкратце:
1) асимметричные криптоалгоритмы, и по логике и по устоявшимся правилам и по скорости, предназначены для двух «вещей»: 1) формирование и проверка электронной подписи (там как по Вашему шифруется и расшифровывается отпечаток блока открытых данных — иными словами ХЭШ); 2) криптосхемы с открытым распределением ключей (там уже шифруется и расшифровывается (либо по Диффи-Хелману, либо по Эль-Гамалю) ключ симметричного криптоалгоритма, на котором зашифрован блок открытой информации);
2) если уж Вы решили хранить приватный ключ в коде в виде константы, то зачем Вам беспокоиться о «недокументированном поведении» дефолтного для Windows криптопровайдера?
3) в РФ разрешено использовать только отечественные криптоалгоритмы, если Вы хотите попасть под защиту законодательства РФ в сфере информационной безопасности (если Вы не из РФ, то Вам это и не нужно)…
Приношу извинения за то, что, не разобравшись, комментировал Вашу разработку с той точки зрения, что Вы находитесь в РФ и, возможно, зря обвинил Вас за «не РФ-овское» использование крипоалгоритмов (все касается только информационной безопасности в ее правовой и законодательной сфере). Так что, из всего выходит, я начал консультировать иностранного гражданина)))))
З.Ы. вот уже зазвонил сотовый без СИМ-ки и аккумулятора) наверное это «младшие братья» «вежливых людей»)))Lence
29.03.2016 12:481) Хочу привести цитату из статьи, ссылка на нее в конце топика:
Криптосистема RSA, предложенная в 1977 году Ривестом (R. Rivest), Шамиром (A. Shamir) и Адлеманом (L. Adleman), предназначена для шифрования и цифровой подписи. В настоящее время RSA является наиболее распространенной криптосистемой — стандартом де-факто для многих криптографических приложений. Криптосистема RSA широко применяется в составе различных стандартов и протоколов Интернета, включая PEM, S/MIME, PEM-MIME, S-HTTP и SSL. Т.е. необязательно для ЭЛ, хотите криптуйте секретным ключом пароль для всего остального сообщения, хотите сравнивайте ХЭШ сообщения итд.
У меня в одном из проектов клиент генерирует md5 из данных в полученной xml, расшифровывает публичным ключом подпись от сервера, тоже в md5, сравнивает. Далее после определенных действий генерирует xml в ответ, в котором содержится сгенерированный md5, зашифрованный публичным ключом. На сервере может быть аппликация с «вшитым» секретным ключом, может висеть PHP скрипт с «вшитым» секретным ключом.
2) Вопрос не о выборе, что использовать, Windows крипто провайдер, OpenSSLили отечественную разработку. Как и не вопрос на чем реализовывать. Вопрос в примере — Delphi + OpenSSL. Как написали выше примеров на Delphi не вагон и тележка, с/с++ да. На Delphi кусочки какие-то. Зашифровать только, а расшифровать?
3) Но примеры то не запрещены?
P.S. я поделился примерами как использовать RSA от OpenSSL в проектах на Delphi, звонить Вам с тапка на выключенный мобильник не буду )))
mefest
28.03.2016 13:23-3На сколько тяжелые библиотеки OpenSSL?
Во времена универа мы сами реализовывали rsa алгоритм, там довольно простой алгоритм.Насколько больше выгода от использования OpenSSL?SannX
29.03.2016 16:46+2В области инф. безопасности лучше не изобретать велосипеды — это чревато дырами. В универе для обучения можно и писать самому такие алгоритмы, а вот в продакшине лучше использовать готовые, хорошо протестированные библиотеки/решения. Потому размер тут не главное.
leremin
28.03.2016 23:56В репе есть примечание "Для компиляции в XE необходимо поменять типы у входящих параметров"… Почему нельзя использовать {$IFDEF VERXXX}?
ValdikSS
Вы уверены, что понимаете, что вы делаете? Функции RSA_private_encrypt и RSA_public_decrypt, несмотря на их название, работают с подписями, а не с шифрованием (с точки зрения RSA, дешифровка публичным ключом является подписью, а шифрование приватным — проверкой подписи).
StrangerInRed
Легчайше. RTFM!
StrangerInRed
Совет на будуще когда имплементите незнакомое шифрование пишите для него тесты где проверяйте для разных данных length(data) == length(encrypted message). В подписе и хеше length(encrypted) всегда будет const для разной длинны data.
Kolyuchkin
Согласен с Вами насчет непонимания автора. Фактически, исходя из логики алгоритмов ЭП, «шифруется и расшифровывается» результат необратимой функции (ХЭШ), а он, автор, пихает вместо ХЭШ-а текстовую строку… Не одобряю я такое… Это все равно, что в стиральной машине тесто месить — можно, но зачем?
ValdikSS
Да дело не в этом. Автор пытается использовать RSA в качестве какого-то симметричного шифра, фактически не используя свойство асимметрии, и, вероятно, из-за непонимания процесса, описывает это в качестве правильного способа использования RSA.
Вот никогда не жаловался на статьи, но это теперь уже, похоже, уровень хабра 2016 года. НЛО должно быть стыдно за то, что за такую статью теперь можно инвайт получить.
Kolyuchkin
Вот и добавить нечего))))) Все в точку)))))
toxicdream
Нда уж…
Читаешь и прям руки чешутся самому написать статью.
Но:
1) нет времени
2) а зачем? кому надо тот сам дойдет — в инете вагон и тележка примеров.
Как-то пришлось переводить один продукт с OpenSSL на SSPI.
В итоге замучившись с трансляцией заголовочных файлов из C++ на Pascal/Delphi, взяли готовый продукт.
Толи Secure Black Box, толи Internet Clever Suite — уже не помню.
Чего и другим советую, когда цена ошибки слишком велика по сравнению с ценой на эти продукты.
Lence
Развею догадки. Понимание вопроса имеется. В статье описывается способ реализации RSA от Open SSL конкретно в Delphi. Но никак не призыв того как правильно или неправильно использовать именно алгоритм RSA. Да, RSA используют для сверки подписей, ХЭШей. Но кто сказал, что не могу зашифровать, к примеру, серийный номер карты на клиенте, передать его в зашифрованном виде не сервер и только сервер может расшифровать переданные данные, если длина серийного номера удовлетворяет нужному размеру и крипто стойкости в 1024 бита мне хватает?
ValdikSS
Как обычно работает RSA: данные шифруются публичным ключом, расшифровываются приватным. Публичный можно, скажем, вшить в программу, и в этом случае, действительно, никто не сможет расшифровать данные, даже обладая программой.
Вы же, однако, используете подписи так, словно это и есть данные: вы дешифруете открытый текст (будто это уже шифрованный текст) приватным ключом, а чтобы его расшифровать, наоборот, шифруете текст публичным ключом.
Таким образом, чтобы зашифровать текст, вам нужно вшить приватный ключ в программу, а не публичный, а получить публичный из приватного — плевое дело. И любой человек, имеющий эту програму и способный достать из нее приватный ключ (а это несложно), способен расшифровывать данные.
Так что, боюсь, вы действительно не понимаете, что делаете.
Lence
Вы в правду считаете, что я буду шифровать данные секретным ключом на клиенте и передавать их в таком виде? Если конкретно в моем случае, то с клиентом рядом идет публичный ключ, а приватный находится на сервере. В конце статьи я показал пример как «вшить» ключ в программу. Еще раз — это примеры, как туда/обратно зашифровать/расшифровать приватным/публичным ключами, на Delphi используя OpenSSL. А не то как я реализовал проект, правильно или нет. Суть статьи не в том, как правильно шифровать: вначале публичным и секретным расшифровывать или наоборот. Все зависит от задачи.
Я смог внести ясность в то, что понимаю о чем пишу?
ValdikSS
Я вас не понимаю. Если это примеры, то почему они неправильные? Еще раз: у вас в статье шифрование осущеставляется приватным ключом, а расшифрование — публичным
Lence
Ясно. Я понял о чем Вы. Взгляните пожалуйста на Github. Утилитка, которая написана имеет 4 кнопки: Public Encrypt и для нее Private Decrypt. И отдельно Private Encrypt и для нее Public Decrypt. Для того чтоб показать: если зашифровать Публичным, то расшифровать можно только Приватным и если зашифровать Приватным, то расшифровать можно только Публичным. Конкретно в моем проекте — Публичным ключом шифруется, а Приватным расшифровывается. Я отредактирую куски, которые смущают.
ComradeAndrew
Я вот тоже не понял. Зачем вы вообще показываете пример с шифрованием приватным ключом в ассиметричной системе шифрования? Тем более, раз утверждаете, что сами так не делаете.
Lence
На самом деле в полном исходном коде реализованы оба способа, как я написал в комментарии выше. Но для ясности подправил исходники и добавил комментарий.
ValdikSS
Как бы технически можно, конечно, шифровать приватным ключом (дешифровать, на самом деле) и расшифровать публичным (а тут на самом деле шифровать), но для данных это совершенно не подходит, т.к. любой, кто владеет приватным ключом, сможет совершать оба действия, вы же понимаете это?
Даже с технической точки зрения, если смотреть со стороны низкого уровня, у вас кнопки делают не то, что на них написано: Private Crypting будет на самом деле расшифровывать данные приватным ключом, а Public Decrypting наоборот, зашифровывать. Именно поэтому у вас образуется «мусор» в виде паддинга, т.к. Public Decrypting (который на самом деле Encrypting) логично считает, что на вход ему подали открытое сообщение, а у вас оно получается уже с паддингом.
Lence
Конечно же я прекрасно понимаю, что из приватного получается публичный, именно таким образом в статье я сгенерировал ключи. Мне казалось это очевидным. Вначале приватный, затем извлек публичный.
Давайте я приведу пример — когда множество клиентов по офису, которые получают сообщения от сервера, будь то настройки или еще какие либо команды. В этом случае сервер имея приватный ключ, шифрует ( по вашей терминологии расшифровывает) сообщение, для клиентов. А клиент, приняв такое сообщение успешно его расшифрует (зашифрует с ссылкой на Вас) публичным ключом и будет точно уверен, что сообщение отправил именно сервер. Поэтому пример работает в обе стороны.
Что касается мусора (это в обоих функциях decrypting публичным или приватным, в приватном точно так) — изначально для data задается размер (в моем примере 128 байт), после дешифрации в data попадает, допустим, всего 27 байт (строка маленькая), остальное место в data остается бинарным мусором. Никак не связанно с паддингом.
В статье исправил порядок функций, чтоб не было путаницы в понимании. Я не стал с самого начала описывать, что такое RSA. цель была в другом, ссылку на отличную статью я привел в конце. Видимо вкратце надо было немного написать.
Lence
Сверка подписи именно таким образом и происходит. Отправителем шифруется приватным ключом и клиент публичным ключом сверяет подпись.
ValdikSS
Хм, ну можно так, да.
Lence
Вас видимо смутил пример с «вшитым» ключом, где как раз приватный ключ в константе, поэтому Вы решили, что с клиентом идет приватный? Публичный ключ у клиента. И он даже не вшит, всегда скачивается, потому, что в любой момент ключи могут поменяться