Я хочу реализовать обмен ключами Диффи-Хеллмана в моем существующем протоколе. Я искал довольно много сайтов на эту тему, и самое большее, что я мог получить, это «вы обмениваетесь параметрами DH между сторонами, и это все». Было явно недостаточно информации по теме или я искал не в том месте.
После нескольких дней работы я нашел хорошие заголовочные файлы OpenSSL для Delphi (https://github.com/Arvur/OpenSSL-Delphi).. Используя эти заголовки, исходный код из OpenSSL и пару примеров программ, я создал полный обмен ключами Диффи-Хеллмана. от генерации параметров DH и открытых ключей до получения общего секрета. Для самого обмена я использовал простые файлы, но его можно использовать любым способом (например, сокетами).
procedure CommonSecretKeyGeneration;
var parameters_context:PEVP_PKEY_CTX;
key_generation_context:PEVP_PKEY_CTX;
key_generation_context2:PEVP_PKEY_CTX;
shared_context1:PEVP_PKEY_CTX;
shared_context2:PEVP_PKEY_CTX;
parameters:PEVP_PKEY;
parameters2:PEVP_PKEY;
private_key:PEVP_PKEY;
private_key2:PEVP_PKEY;
public_key:PEVP_PKEY;
public_key2:PEVP_PKEY;
bio_file:PBIO;
keylen:cardinal;
arr:array of byte;
str:string;
i:integer;
begin
SSL_InitEVP;
SSL_InitBIO;
SSL_InitPEM;
Log('-------------Generating parameters---------------');
parameters_context:=EVP_PKEY_CTX_new_id(EVP_PKEY_DH, nil);
if parameters_context<>nil then Log('Context for parameters created')
else
begin
Log('Failed to generate parameters context');
exit;
end;
i:=EVP_PKEY_paramgen_init(parameters_context);
if i=1 then Log('Parameter context init done')
else
begin
Log('Error initializing parameter context, return='+inttostr(i));
exit;
end;
//equal to EVP_PKEY_CTX_set_dh_paramgen_prime_len(parameters_context, 1024)
i:=EVP_PKEY_CTX_ctrl(parameters_context, EVP_PKEY_DH, EVP_PKEY_OP_PARAMGEN, EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN, 1024, nil);
if i=1 then Log('Setting prime length done')
else
begin
Log('Error setting prime length, return='+inttostr(i));
exit;
end;
i:=EVP_PKEY_paramgen(parameters_context,@parameters);
if i=1 then Log('Generation of parameters sucsessful')
else
begin
Log('Error while generating parameters, return='+inttostr(i));
exit;
end;
Log('-------------Output parameters to file---------------');
//writing parameters to file
bio_file:=BIO_new_file('C:\DH_parameters_test.pem', 'w');
i:=PEM_write_bio_Parameters(bio_file,parameters);
Log('PEM write return='+inttostr(i));
BIO_free_all(bio_file);
Log('-------------Client reading parameters---------------');
//sending parameters via sockets
//reading parameters
bio_file:=BIO_new_file('C:\DH_parameters_test.pem', 'r');
parameters2:=PEM_read_bio_Parameters(bio_file,nil);
BIO_free_all(bio_file);
if parameters2<>nil then Log('Readed parameters sucsess for client')
else
begin
Log('Error reading parameters');
exit;
end;
Log('-------------Generating private/public keys on server---------------');
//making a key on server
key_generation_context:=EVP_PKEY_CTX_new(parameters, nil);
if key_generation_context<>nil then Log('Created key generation context')
else
begin
Log('Error creating key generation context');
exit;
end;
i:=EVP_PKEY_keygen_init(key_generation_context);
if i=1 then Log('Initialized key generation sucsessfully')
else
begin
Log('Error initializing key generation, return='+inttostr(i));
exit;
end;
i:=EVP_PKEY_keygen(key_generation_context, @private_key);
if i=1 then Log('Generated keys')
else
begin
Log('Error generating keys, return='+inttostr(i));
exit;
end;
Log('-------------Generating private/public keys on client---------------');
//making key on client
key_generation_context2:=EVP_PKEY_CTX_new(parameters2, nil);
if key_generation_context<>nil then Log('Created key generation context on client')
else
begin
Log('Error creating key generation context on client');
exit;
end;
i:=EVP_PKEY_keygen_init(key_generation_context2);
if i=1 then Log('Initialized key generation sucsessfully on client')
else
begin
Log('Error initializing key generation on client, return='+inttostr(i));
exit;
end;
i:=EVP_PKEY_keygen(key_generation_context2, @private_key2);
if i=1 then Log('Generated keys on client')
else
begin
Log('Error generating keys, return='+inttostr(i));
exit;
end;
Log('-------------Public key exchange---------------');
Log('-------------Outputing public key on server---------------');
//outputing public keys
bio_file:=BIO_new_file('C:\public_key1.pem', 'w');
i:=PEM_write_bio_PUBKEY(bio_file,private_key);
Log('PEM write return='+inttostr(i));
BIO_free_all(bio_file);
Log('-------------Outputing public key on client---------------');
bio_file:=BIO_new_file('C:\public_key2.pem', 'w');
i:=PEM_write_bio_PUBKEY(bio_file,private_key2);
Log('PEM write return='+inttostr(i));
BIO_free_all(bio_file);
//public key exchange
Log('-------------Reading public key1 (for client)---------------');
//reading public keys
bio_file:=BIO_new_file('C:\public_key1.pem', 'r');
public_key:=PEM_read_bio_PUBKEY(bio_file,nil,nil,nil);
BIO_free_all(bio_file);
if public_key<>nil then Log('Readed public key1 sucsessfully')
else
begin
Log('Error reading public key1');
exit;
end;
Log('-------------Reading public key2 (for server)---------------');
bio_file:=BIO_new_file('C:\public_key2.pem', 'r');
public_key2:=PEM_read_bio_PUBKEY(bio_file,nil,nil,nil);
BIO_free_all(bio_file);
if public_key2<>nil then Log('Readed public key2 sucsessfully')
else
begin
Log('Error reading public key2');
exit;
end;
Log('-------------Calculating secret on server---------------');
shared_context1:=EVP_PKEY_CTX_new(private_key,nil);
if shared_context1<>nil then Log('Created shared context for key1')
else
begin
Log('Error creating shared context for key1');
exit;
end;
i:=EVP_PKEY_derive_init(shared_context1);
if i=1 then Log('Derive init sucsessful on server')
else
begin
Log('Error while initializing derive on server, return='+inttostr(i));
exit;
end;
i:=EVP_PKEY_derive_set_peer(shared_context1,public_key2);
if i=1 then Log('Set peer on server done')
else
begin
Log('Error setting peer on server');
exit;
end;
keylen:=0;
Log('Before query secret key length='+inttostr(keylen));
i:=EVP_PKEY_derive(shared_context1,nil,cardinal(@keylen));
if i=1 then Log('Query secret key length sucsessful, len='+inttostr(keylen))
else
begin
Log('Error query secret key length');
exit;
end;
setlength(arr,keylen);
FillChar(arr[0],length(arr),0);
i:=EVP_PKEY_derive(shared_context1,@arr[0],cardinal(@keylen));
if i=1 then
begin
Log('Output secret key sucsessful, len='+inttostr(keylen));
str:='';
for i:=0 to length(arr)-1 do
str:=str+inttohex(arr[i],2);
Log('Secret on server='+str);
end
else
begin
Log('Error query secret key');
exit;
end;
Log('-------------Calculating secret on client---------------');
shared_context2:=EVP_PKEY_CTX_new(private_key2,nil);
if shared_context1<>nil then Log('Created shared context for key2')
else
begin
Log('Error creating shared context for key2');
exit;
end;
i:=EVP_PKEY_derive_init(shared_context2);
if i=1 then Log('Derive init sucsessful on client')
else
begin
Log('Error while initializing derive on client, return='+inttostr(i));
exit;
end;
i:=EVP_PKEY_derive_set_peer(shared_context2,public_key);
if i=1 then Log('Set peer on client done')
else
begin
Log('Error setting peer on client');
exit;
end;
keylen:=0;
Log('Before query secret key length='+inttostr(keylen));
i:=EVP_PKEY_derive(shared_context2,nil,cardinal(@keylen));
if i=1 then Log('Query secret key length sucsessful, len='+inttostr(keylen))
else
begin
Log('Error query secret key length');
exit;
end;
setlength(arr,keylen);
FillChar(arr[0],length(arr),0);
i:=EVP_PKEY_derive(shared_context2,@arr[0],cardinal(@keylen));
if i=1 then
begin
Log('Output secret key sucsessful, len='+inttostr(keylen));
str:='';
for i:=0 to length(arr)-1 do
str:=str+inttohex(arr[i],2);
Log('Secret on client='+str);
end
else
begin
Log('Error query secret key');
exit;
end;
Log('===============DONE================');
end;
Вопрос:
- Я пропустил какую-либо важную часть обмена ключами DH? Достаточно ли этого, чтобы гарантировать, что обе стороны будут иметь один и тот же секретный ключ?