Import RSA OpenSSL keys with Delphi and Indy
Marion Candau
Marion Candau
[SHOWTOGROUPS=4,20]
OpenSSL is an open-source encryption library implementing TLS and its predecessor SSL. It is widely used by websites in HTTPS. Some Indy 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:
To use the OpenSSL library on Windows, OSX or iOS, it must be installed on your machine. Embarcadero explains how to do it here: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/OpenSSL
For Android, there are two cases:
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.
Load PEM files
We will load the content of the PEM files into a PBIO.
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—–.
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—–.
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:
The FromOpenSSLPrivateKey method is defined as follows:
You can find the full source code for the class here .
In conclusion, we can use OpenSSL with Delphi thanks to Indy. However, it should be kept in mind that many vulnerabilities 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 good practices in cryptography , in particular concerning the recommended minimum key lengths which can be found on https://www.keylength.com/fr/compare/
[/SHOWTOGROUPS]
OpenSSL is an open-source encryption library implementing TLS and its predecessor SSL. It is widely used by websites in HTTPS. Some Indy 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)
To use the OpenSSL library on Windows, OSX or iOS, it must be installed on your machine. Embarcadero explains how to do it here: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/OpenSSL
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: https://indy.fulgan.com/SSL/
Код:
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 ;
Код:
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 ;
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 ;
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 ;
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 ;
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;
Код:
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 ;
In conclusion, we can use OpenSSL with Delphi thanks to Indy. However, it should be kept in mind that many vulnerabilities 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 good practices in cryptography , in particular concerning the recommended minimum key lengths which can be found on https://www.keylength.com/fr/compare/
[/SHOWTOGROUPS]