Articles Import RSA OpenSSL keys with Delphi and Indy by Marion Candau

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
Import RSA OpenSSL keys with Delphi and Indy
Marion Candau
[SHOWTOGROUPS=4,20]
Для просмотра ссылки Войди или Зарегистрируйся is an open-source encryption library implementing TLS and its predecessor SSL. It is widely used by websites in HTTPS. Some Для просмотра ссылки Войди или Зарегистрируйся components use OpenSSL like TIdServerIOHandlerSSLOpenSSL or TIdSSLIOHandlerSocketOpenSSL. Please note, Indy uses OpenSSL version 1.0.2 which is not the latest version.

In this article, we will use the IdSSLOpenSSLHeaders and IdSSLOpenSSL files from Indy. For iOS, we will also need IdSSLOpenSSLHeaders_Static.

The goal is to import the RSA keys contained in a file in PEM format so that they can be read or used later. We will treat three cases:

  • A public key
  • A certificate
  • A private key (encrypted or not)
Load OpenSSL
To use the OpenSSL library on Windows, OSX or iOS, it must be installed on your machine. Embarcadero explains how to do it here: Для просмотра ссылки Войди или Зарегистрируйся

For Android, there are two cases:
  • if you have a version <6.0 then you have nothing to do, OpenSSL is included by default.
  • if you have a version> = 6.0 then you need libcrypto.so * and libssl.so * files (for example, libcrypto.so.1.0.0). They must be deployed in ./assets/internal so that they are accessible via GetDocumentsPath. You can find versions here: Для просмотра ссылки Войди или Зарегистрируйся
Then you have to load the library into your program. The functions that we will need are in the crypto part of OpenSSL. We do not need to load the library on iOS because it is statically linked to the program.
Код:
function  TFromOpenSSL.LoadSSLCryptoLibrary : HMODULE;
begin 
{$ IFDEF MSWINDOWS} 
Result: = SafeLoadLibrary ( 'libeay32.dll' );
{$ ELSE} 
{$ IFDEF ANDROID} 
Result: = LoadLibrary ( 'libcrypto.so' );
{$ ELSE} 
{$ IFNDEF IOS} 
Result: = LoadLibrary ( 'libcrypto.dylib' );
{$ ENDIF} 
{$ ENDIF} 
{$ ENDIF} 
end ;

This function is used in loading the entire OpenSSL library. Some functions that we will use later are not loaded in IdSSLOpenSSLHeaders so we indicate that they are in the FSSLModule.
Код:
function  TFromOpenSSL.LoadOpenSSLLibrary : Boolean;
begin 
{FSSLModule is the private HMODULE of the class into which libcrypto is loaded} 
  if FSSLModule <> 0  then
    Exit (True);
{$ IFDEF ANDROID}
  IdOpenSSLSetLibPath (System.IOUtils.TPath.GetDocumentsPath);
{$ ENDIF}
  Result: = IdSSLOpenSSL.LoadOpenSSLLibrary;
  if Result then 
  begin 
{$ IFNDEF IOS}
    FSSLModule: = LoadSSLCryptoLibrary;
    if FSSLModule = 0  then
      Exit (False);
{We indicate where are the functions not loaded in IdSSLOpenSSLHeaders} 
    PEM_read_bio_PUBKEY: = GetProcAddress (FSSLModule, PChar ( 'PEM_read_bio_PUBKEY' ));
    X509_get_pubkey: = GetProcAddress (FSSLModule, PChar ( 'X509_get_pubkey' ));
    EVP_PKEY_get1_RSA: = GetProcAddress (FSSLModule, PChar ( 'EVP_PKEY_get1_RSA' ));
    OpenSSL_add_all_algorithms;
    OpenSSL_add_all_ciphers;
    OpenSSL_add_all_digests;
    ERR_load_crypto_strings;
{$ ENDIF} 
  end ;
end ;

Load PEM files
We will load the content of the PEM files into a PBIO.
Код:
function  TFromOpenSSL.LoadPEMFile (filePath: string): PBio;
var 
{$ IFNDEF MSWINDOWS}
  LEncoding: TEncoding;
  LOffset: Integer;
{$ ENDIF}
  Buffer: TBytes;
  Stream: TStream;
begin 
  Stream: = TFileStream.Create (filePath, fmOpenRead or fmShareDenyWrite);
  try
    SetLength (Buffer, Stream.size);
    Stream.ReadBuffer (Buffer [ 0 ], Stream.size);
{$ IFNDEF MSWINDOWS}
{We deal with stream encoding problems on different Windows platforms} 
    LEncoding: = nil ;
    LOffset: = TEncoding.GetBufferEncoding (Buffer, LEncoding);
    Buffer: = LEncoding.Convert (LEncoding, TEncoding.UTF8, Buffer, LOffset,
      Length (Buffer) - LOffset);
{$ ENDIF}
    Result: = BIO_new_mem_buf (Buffer, Length (Buffer));
  finally
    Stream.free;
  end ;
end ;

Import an RSA public key
A PEM format file containing an RSA public key begins with —–BEGIN PUBLIC KEY—– then is followed by the key in Base64 and ends with —–END PUBLIC KEY—–.
Код:
function  TFromOpenSSL.FromOpenSSLPublicKey (filePath: string): pRSA;
var
  KeyBuffer: PBIO;
  pkey: PEVP_PKEY;
begin
  KeyBuffer: = LoadPEMFile (filePath);
  if KeyBuffer = nil  then 
    raise Exception.Create ( 'Unable to load buffer' );
  try 
    pkey: = PEM_read_bio_PUBKEY (KeyBuffer, nil , nil , nil );
    if  not Assigned (pkey) then 
      raise Exception.Create ( 'Unable to load public key' );
    try
      Result: = EVP_PKEY_get1_RSA (pkey);
      if  not Assigned (Result) then 
        raise Exception.Create ( 'Unable to load RSA public key' );
    finally
      EVP_PKEY_free (pkey);
    end ;
  finally
    BIO_free (KeyBuffer);
  end ;
end ;

Import an RSA public key from an X509 certificate
A file in PEM format containing an X509 certificate begins with —–BEGIN CERTIFICATE—– then is followed by the key in Base64 and ends with —–END CERTIFICATE—–.
Код:
function  TFromOpenSSL.FromOpenSSLCert (filePath: string): pRSA;
var
  KeyBuffer: PBIO;
  FX509: pX509;
  Key: PEVP_PKEY;
begin
  KeyBuffer: = LoadPEMFile (Buffer, Length (Buffer));
  if KeyBuffer = nil  then 
    raise Exception.Create ( 'Unable to load buffer X509' );
  try 
    FX509: = PEM_read_bio_X509 (KeyBuffer, nil , nil , nil );
    if  not Assigned (FX509) then 
      raise Exception.Create ( 'Unable to load X509 certificate' );
    Key: = X509_get_pubkey (FX509);
    if  not Assigned (Key) then 
      raise Exception.Create ( 'Unable to load public key X509' );
    try
      Result: = EVP_PKEY_get1_RSA (Key);
      if  not Assigned (Result) then 
        raise Exception.Create ( 'Unable to load RSA public key' );
    finally
      EVP_PKEY_free (Key);
    end ;
  finally
    BIO_free (KeyBuffer);
  end ;
end ;

Import an RSA private key (encrypted or not)
A PEM format file containing an RSA private key starts with —–BEGIN PRIVATE KEY—– then is followed by the key in Base64 and ends with —–END PRIVATE KEY—–. If the key is encrypted, then the file in PEM format begins with —–BEGIN RSA PRIVATE KEY—– then is followed by Proc-Type: 4, ENCRYPTED. Then there is information about the algorithm used to encrypt the key (eg AES-128-CBC) and then there is the encrypted key, in Base64. Finally, the file ends with —–END RSA PRIVATE KEY—–. In the latter case, the function must have as input the password, pwd, to decrypt the key. The PEM_read_bio_RSAPrivateKey function takes a PAnsiChar (on Windows or OSX) or a PByte (on mobile platforms) as the fourth argument. This argument can contain the password. So we declared the PReadKeyChar type like this:
Код:
{$ IF (defined (MSWINDOWS) or defined (MACOS)) and (not defined (IOS))}
ReadKeyChar = AnsiChar;
{$ ELSE}
ReadKeyChar = Byte;
{$ ENDIF}
PReadKeyChar = ^ ReadKeyChar;

The FromOpenSSLPrivateKey method is defined as follows:
Код:
function  TFromOpenSSL.FromOpenSSLPrivateKey (filePath: string;
  pwd: String): pRSA;
var
  KeyBuffer: PBio;
  p: PReadKeyChar;
  I: Integer;
begin
  KeyBuffer: = LoadPEMFile (filePath);
  if KeyBuffer = nil  then 
    raise Exception.Create ( 'Unable to load buffer' );
  try 
    if pwd <> ''  then 
    begin
      p: = GetMemory ((pwd.Length + 1) * SizeOf (Char));
      for I: = 0 to pwd.Length - 1 do
        p [I]: = ReadKeyChar (pwd.Chars [I]);
      p [pwd.Length]: = ReadKeyChar (# 0);
    end 
    else 
      p: = nil ;
    try 
      Result: = PEM_read_bio_RSAPrivateKey (KeyBuffer, nil , nil , p);
      if  not Assigned (Result) then 
        raise Exception.Create ( 'Unable to load RSA private key' );
    finally 
{We erase the password} 
      FillChar (p, SizeOf (p), 0 );
      FreeMem (p);
    end ;
  finally
    BIO_free (KeyBuffer);
  end ;
end ;

You can find the full source code for the class Для просмотра ссылки Войди или Зарегистрируйся .

In conclusion, we can use OpenSSL with Delphi thanks to Indy. However, it should be kept in mind that Для просмотра ссылки Войди или Зарегистрируйся are found in OpenSSL and many algorithms used are obsolete, in particular the algorithm used in PEM format to derive a key from a password, because it uses l MD5 algorithm. You must therefore be vigilant and follow Для просмотра ссылки Войди или Зарегистрируйся , in particular concerning the recommended minimum key lengths which can be found on Для просмотра ссылки Войди или Зарегистрируйся

[/SHOWTOGROUPS]