Search code examples
delphiopensslrsadelphi-10.2-tokyo

Using OpenSSL to Generate KeyPairs


I'm using delphiopenssl wrapper to generate .pem format key files. I'm using Generate RSA Key example to generate these keys.

What I need

Two days ago I was hoping to find a simple way to generate RSA keys and use them to encrypt/dcrypt some strings or TBytes Buffer. Now after searching every possible solution I decided to use OpenSSL todo the job

My problem

the thing is I cant create the files with inputted file name to the function. But I still get a file named 'C' or 'm' (without an extension) that contains the public and private key together in PEM format and what is weirder is I'm giving the function two file names for Public and Private key

What I tried

  • I looked for the documentation for the openssl methods used so I know how to debug this but with no success.
  • I tried to use the Openssl.exe command line to do the same thing and it worked so I know it is not a bug in openssl.
  • at first I could not Compile the code because of [dcc32 Error] CryptoUtils.pas(399): E2010 Incompatible types: 'PAnsiChar' and 'PWideChar' and my solution was to type cast to PAnsiChar where needed and made sure that all input is ansistring so I could try a non Unicode test but with no success.
  • When I debug this The name of the file reaches until the function call where I get 0 as a result (in the vague docs 0 means success) but I always get the same result (either a C or M file name in the output directory).
  • I tried to look for other implementations in other languages with documentation but again no true solution every one has magically solved it without problems

this the code responsible

procedure GenerateKeyPair;
var
  kp: TKeyPairGenerator;
begin
kp := TKeyPairGenerator.Create;
kp.KeyFileNames(GetCurrentDir + '\mykeys');  // it create a pair c:\temp\mykeys.key
                                    // and c:\temp\mykeys.pub
kp.Password := 'mypasswd';          // Required
kp.GenerateRSA;
end;


procedure TMainForm.Button2Click(Sender: TObject);
begin
  InitOpenSSL;

  GenerateKeyPair;

  FreeOpenSSL;
end;

function TKeyPairGenerator.GenerateRSA: Integer;
var
  rsa: pRSA;
  PrivateKeyOut, PublicKeyOut, ErrMsg: pBIO;
  buff: array [0..1023] of char;
  enc: pEVP_CIPHER;

begin

Result := 0;

if (fPrivateKeyFile = '') or (fPublicKeyFile = '') then
  raise EOpenSSL.Create('Key filenames must be specified.');
if (fPassword = '') then
  raise EOpenSSL.Create('A password must be specified.');

ERR_load_crypto_strings;
OpenSSL_add_all_ciphers;

enc := EVP_des_ede3_cbc; ///??????

// Load a pseudo random file
RAND_load_file(PAnsiChar(fSeedFile), -1);

rsa := RSA_generate_key(fKeyLength, RSA_F4, nil, ErrMsg);
if rsa=nil then
  begin
  BIO_reset(ErrMsg);
  BIO_read(ErrMsg, @buff, 1024);
  raise EOpenSSL.Create(PChar(@buff));
  end;

PrivateKeyOut := BIO_new(BIO_s_file());
if BIO_write_filename(PrivateKeyOut, PAnsiChar(fPrivateKeyFile)) <> 0 then Result := - 1; // I get a 1 here meaning failure whit out any useful info
PublicKeyOut := BIO_new(BIO_s_file());
if BIO_write_filename(PublicKeyOut, PAnsiChar(fPublicKeyFile)) <> 0 then Result := ERR_peek_last_error; // ERR_peek_last_error returns 0

PEM_write_bio_RSAPrivateKey(PrivateKeyOut, rsa, enc, nil, 0, nil, PChar(fPassword));
PEM_write_bio_RSAPublicKey(PublicKeyOut, rsa);

if rsa <> nil then RSA_free(rsa);
if PrivateKeyOut <> nil then BIO_free_all(PrivateKeyOut);
if PublicKeyOut <> nil then BIO_free_all(PublicKeyOut);
end;

and this is the content of the file named C

-----BEGIN RSA PUBLIC KEY-----
MIIBBgKB/gC1aSSL+rlH/owIISeoNNO9mVmlPfWVsnRloFUHlYQMZyVovcTHZZhd
CvweTjMlwRHTqNAnX3CpFSwjcf5FVyiB7qoWQHDXlTSLD4kFQzUfGVTorwuB1jii
Su3tt3GCJE//xE5RWrsAIETuxIk2ZSkf4T0htAu44gBbup7CT4cSOaUeTr6/D9WL
xl+jGCi9d4oG+JkVJ21VHl1O5/UG4HRKiKx+PfNrBZvR4COVzYV6clXv7fd2EZo7
Gbz/d3yUG9jVMuQmbSDA0Ew3vE9iYTIpXeGSM1aZKgkOWqehO7b8yIqhmUbW2Yl5
sydL/xx7WEsQuTqvPST1lkpfdyIpAgMBAAE=
-----END RSA PUBLIC KEY-----
xng1PPL79FUIjo1i3Fjg1qagYELOy023nnekp9ZzgPrVsuZT4fnXTaqFHoOjhTr
IqhHVMsaUIxG0OOdmkDZzWbHjlJbA3BpNvB0NqSlb8vQrg+d9Tq4wh4heKNl/Wim
IocSUi3qULEC29H2rA2VnutilcpDTcc4fiKwWlAnYGOQieVHMnNGP+RrCqjIzurN
M4aTK7mRna3OGYOZBl89xDd9elmYtToFrb/aVEgE2FY3190AosRyb/9bjR+ol39Y
XrtXKAPOwGum6O9Vc5jTAw6wC6OTpCTZXw6NuIm6WST0u5Aknvc0mGEx/w8yYxil
fJvavRADkWIBYLvWj5tzm2pOeT3C1SoCqtEwmpK4eZpR+TeODsje4blaGIeIMtOl
BOnGZSy8KLk36kH2mT6pO3/QHNK7yZEhyd9uw7Hol8pFk8ZrCPu25vju8UnC0/k6
djExrjl2+V/EGeO8k79W2kpHjIIcY5SG+hNI909j/OIwJH8UJCmQrSPaxSloUquy
upQPhPhaz1UKrZJw/u7oKHzeYPFI9NmWBbz18Iax52wsmHaw/mqwt8h96wUujVB2
CWG0IfW+LF7r6rZ6wREW72ldMLiKpGAucbaGBpGKbMLpbai9BTmkpdtpi/PH7yOG
8/gS2Av6VvD2wzdNt94Zqqlz+qN3K/t4qIjOIBHSpCcm7DJLQWK452xheZpiYlP0
hBAX7V9WvnQcuhfiX0zm097dmqcjySzpKL53T7dk3dNoHsbPPzKrS/WDMvGZLMwF
4hifva+O3lnI/DlBZymcPNlPNwTTztDDWxFwv2Y9jq20yXbJLjEP1Qjpk+oS9dVh
HTTun0ZkrfzVaXcfcWggKWrpiTIen2jCqqVSMyS0xe4h9v+/gjkMA54OzN+zPyIH
zpgRmHxVIXYQ+AuK1zEf6lZaeeUiKWG4ywpgML3X4Ln5SWNZA0iLYQKr98XDn3VJ
WoVA1sVqsi2cuq+9Td2Z9TbD4FCxZlrrOZCN5x+YaMjp+KzFA5m+7rEvS96Z+Kit
Pw1mZkrQ2QioXOmkDiqypFk08Z8BiPIb+hklXrrD7Vkp3VdMO9UQpKppfZFMQ0mG
6OGcf51kBKtfEPcHEBkQM/sPw5H4zC+pRaxBseL/5Fzcq/B5ywPzEjMfQc4sjpTi
uFZFA9rVzikCOEv1R8MPrdiFKzrBv7xR1SjA+W8DeTJaeXmHRTzT75rovvH2GUvP
RUMyGKfp1MXIFzyU5FA4xgPVPve2K/+P
-----END RSA PRIVATE KEY-----

My question how to solve this or Is there any docs about those methods I can read .

OpenSSL version 1.0.2 from Indy servers.

Side note: I submitted a feature request (System.Cryptography) for whom interested to vote for the cause


Solution

  • I instead, usually create the public and private key separately using PEM_write_bio_* APIs to create TBytes. Once you have the TBytes for the public and private key you can use Delphi's TFile.WriteAllBytes() in the System.IOUtils unit to save the TBytes to a file.

    function CreateCertificate_PKCS(out APublicKey, APrivateKey: TBytes): Boolean;
    var
      Bignum: PBIGNUM;
      RSA: PRSA;
      PrivateKey, PublicKey: PBIO;
      KeyLength: Integer;
    begin
      Result := False;
      Bignum := BN_new();
      try
        if BN_set_word(Bignum, RSA_F4) = 1 then
        begin
          RSA := RSA_new;
          try
            if RSA_generate_key_ex(RSA, 2048, Bignum, nil) = 1 then
            begin
              { Write the public key }
              PublicKey := BIO_new(BIO_s_mem);
              try
                PEM_write_bio_RSAPublicKey(PublicKey, RSA);
                KeyLength := BIO_pending(PublicKey);
                SetLength(APublicKey, KeyLength);
                BIO_read(PublicKey, @APublicKey[0], KeyLength);
              finally
                BIO_free(PublicKey);
              end;
    
              { Write the private key }
              PrivateKey := BIO_new(BIO_s_mem);
              try
                PEM_write_bio_RSAPrivateKey(PrivateKey, RSA, nil, nil, 0, nil, nil);
                KeyLength := BIO_pending(PrivateKey);
                SetLength(APrivateKey, KeyLength);
                BIO_read(PrivateKey, @APrivateKey[0], KeyLength);
              finally
                BIO_free(PrivateKey);
              end;
    
              Result := True;
            end;
          finally
            RSA_free(RSA);
          end;
        end;
      finally
        BN_free(Bignum);
      end;
    end;