4

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

Nasreddine Galfout
  • 2,550
  • 2
  • 18
  • 36
  • I doubt whether it is the cause of your problem but `GetCurrentDir + '\mykeys'` looks like an accident wating to happen. E.g. what if the app has insufficient rights in relation to whereever `GetCurrentDir` happens to return? – MartynA Mar 18 '19 at 22:33
  • @MartynA I'm running the app from documents dir so its fine, I never use this type of code, the path to the real product name is Unicode, as I said I was trying to conduct a non Unicode test because of PAnsiChar casting – Nasreddine Galfout Mar 19 '19 at 04:04

2 Answers2

3

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;
Allen Drennan
  • 291
  • 3
  • 6
  • Thanks I'm on my phone now I will try this and get back to you. – Nasreddine Galfout Mar 19 '19 at 11:40
  • Are you using the IdSSLOpenSSLHeaders.pas in this example? – Nasreddine Galfout Mar 19 '19 at 12:42
  • Yes, this works with the indy headers and works for 1.0.x and 1.1.x versions of openssl. To use it with just the Indy header file IdSSLOpenSSLHeaders you probably need to call Load() before using this routine. – Allen Drennan Mar 19 '19 at 13:39
  • Thank you very much for this, you have saved me from a lot of pressure. I will write a detailed answer of how I solved my problem (generate, encrypt, decrypt) using your code for future readers – Nasreddine Galfout Mar 19 '19 at 14:17
  • Glad it helped you solve the problem. I usually avoid using library APIs to write or read from disk because you never know how they are handling write errors, filenames and paths. It's better to handle that yourself. – Allen Drennan Mar 19 '19 at 14:47
  • Allen read my answer. I hope using your code in the GitHub repo is not a problem thanks again – Nasreddine Galfout Mar 19 '19 at 17:43
3

This is my solution to the problem in question

The problem: Generate an RSA Key Pairs (public/private) and then read them from file, stream or even a TBytes buffer.

Solutions Found:

  • the first thing that you can find (google) is the Turbo Pack LockBox for Delphi but the problem is that it does not support above 1048 bit key sizes (at the time of writing) which is way below the current minimum key size (2048 at the time of writing), So you could omit that from possible solutions (though it has a native implementation of what I need).
  • a Wrapper for CryptoAPI. The Idea is beautiful because you get the chance to use Microsoft own Crypto Library which will make it easier for deployment (since it is built in the OS it self). However there is little support for Pascal (it is written in c++) and would require tremendous work to support and maintain and it is not cross platform.
  • a Wrapper for OpenSSl. Now that is an awesome Idea because the library is already used by one of the great open source projects Indy and searching for other open source projects would have higher possibility of success

    1. zizzo81/delphiopenssl has its own wrapper for OpenSSL and great demos but as the question states there is a problem or two in that source code and I could not post an issue on GitHub for one good reason the project is not maintained and it is only an imported version from an old site.
    2. lminuti/Delphi-OpenSSL is based on Indy Wrappers IdSSLOpenSSLHeaders.pas and provides basic features for this problem (encrypt/decrypt but not generate).

Finale Solution:

at the end I needed to add something to lminuti/Delphi-OpenSSL for it to be completed and thanks for allen-drennan answer to this question it was very easy.

I also forked the lminuti/Delphi-OpenSSL to present the changes concerned by this question (the fork contains a ready to use tutorial for this problem)

Bugs I found and fixed the current wrapper in the base project uses PEM_read_bio_PubKey to read Public key files in Traditional PEM formats (which is not the standard anymore) also it uses PEM_write_bio_RSAPrivateKey which is deprecated as well (I did not fix this for the moment)

References I found useful:

Nasreddine Galfout
  • 2,550
  • 2
  • 18
  • 36
  • Thank you very much the detailed solution! After 3 years Indy is still at OpenSSL 1.0, while the recommended version would be 3.0 now. *(1.1 will be also deprecated after 2023-sept).* **Do you know any library that is compatible with SSL 3.0?** – SzakiLaci Apr 11 '22 at 12:32
  • @SzakiLaci for new development and consuming REST APIs I would suggest using the standard controls THTTPClient. for WebSocket there is a payed library that is excellent for this. – Nasreddine Galfout Apr 12 '22 at 11:20
  • 1
    @SzakiLaci there is also this http://wiki.overbyte.eu/wiki/index.php/ICS_Download – Nasreddine Galfout Apr 12 '22 at 11:22