27 July, 2021

Working with cryptography in EurekaLog

EurekaLog is an exception tracer, i.e. a tool that installs hooks and catches exceptions being thrown, allowing you to generate a report on unhandled exceptions. However, it does provide various kinds of additional functionality that you can use in your apps. And one of those features is cryptography functions.

EurekaLog offers 3 units:
  1. EEncoding - contains data encoding and transformation functions;
  2. EHash - contains hash functions;
  3. EEncrypt - contains symmetric and asymmetric encryption functions.
Although the functions from these units will not be able to fully replace a proper cryptographic support library, it may be enough for you in some special cases.

Important Note: please update EurekaLog to the most recent version. Not all features described here are available in previous versions, because some features were published specifically for this article.

Encoding

Before talking about cryptography, you need to take decicion about data representation. For example, suppose you want to get the MD5 hash of the 'Привет' string (means "Hello" in Russian, reads as "Privet", stress on the second syllable). What exactly are you gonna feed into hash function? $CF$F0$E8$E2$E5$F2 bytes? (which is 'Привет' encoded via ANSI/Windows-1251) Or $1F$04$40$04$38$04$32$04$35$04$42$04 bytes? ('Привет' in Unicode/UTF-16) Or may be $D0$9F$D1$80$D0$B8 bytes ('Привет' in UTF-8)? Depending on how you answer this question, you will get different results. For example, the MD5 hash for the 'Привет' in UTF-16 would be 8EFA2364EE560EE1B862ECC8D430C9AD, for 'Привет' in ANSI - 43A3F987A7AF93811B7682E43ED0752A, and for 'Привет' in UTF-8 - 8A669E9418750C81AB90AE159A8EC410.

Questions like this probably don't matter if you use cryptography functions exclusively inside your own apps. But as soon as you need to interact with other code - you immediately would have problems with the exact definition of the data.

Therefore, when you want exact result, you should operate on bytes, not strings. In Delphi, to operate on bytes, you can:
  • Use pointer + size of data: (const ABuffer: Pointer; const ABufferSize: Cardinal);
  • Use TBytes (array of Byte - dynamic byte array);
  • Use RawByteString;
  • Use TStream (its sub-classes).

Specifically, EurekaLog functions accepts pointer+size, as well as overloaded option for RawByteString.

For example, if you try to obtain MD5-hash from "just" string 'Привет' in PHP - you would get 8a669e9418750c81ab90ae159a8ec410 - i.e. MD5-hash of UTF-8 encoded 'Привет'.
From where you can also conclude that strings in PHP are stored in UTF-8; for comparison: Delphi stores strings as UTF-16 (since Delphi 2009) or ANSI (Delphi 2007 and earlier).
If you want to change the encoding in PHP, you will need to call something like mb_convert_encoding. And if you want to change the encoding in Delphi, you need Delphi encoding functions. Specifically, to convert to/from UTF-8, TEncoding. In Delphi 2009 and up, you can also just declare the string type of the desired encoding and string data conversion will be done automatically when assigned.

The same is true in the opposite direction: the result of calling cryptographic functions is a set of bytes (hash, encrypted data, etc.). If you want to display these bytes to a human, you have to convert it to a string. It can be done, again, in different ways. For example, you can use the built-in function BinToHex or its more convenient equivalents: HexEncodeString/HexEncodeToString from EurekaLog. You can use Base64EncodeString/Base64EncodeToString from EurekaLog. If, suddenly, you need to convert data from/to RawByteString, then EurekaLog has RAWToString/RAWFromString helpers. Also you may want to load/save small data directly to files - there is FileToString/StringToFile for that (from the ECompatibility unit).

Examples of using the mentioned functions can be found below.


Hashing

EurekaLog has functions for calculating the following hashes:
  • CRC16
  • CRC32
  • MD5
  • SHA-1
  • SHA-256
  • SDBM is a good general purpose hash function with uniform distribution, convenient for use as a key/index in a database
All hashing functions have name like HashNameHash (for example, MD5Hash()), returns result of THashNameHash type (for example, TSHA1Hash), and accepts RawByteString on input, as well as pointer+size (overloaded option).

Additionally, EurekaLog has HMAC implementation for some hashes. One way to use HMAC is to authenticate a user by combining a salt and a password to obtain hash via HMAC. HMAC functions have names like HashNameHMAC (for example, MD5HMAC()) and accepts password and salt on input.

Here are some practical examples:

1. Calculate hash of a string:
uses
  EEncoding, // for HexEncodeToString
  EHash;     // for MD5Hash

procedure TForm1.Button1Click(Sender: TObject);
var
  S: String;           // Source string
  UTF8Str: UTF8String; // Byte representation of a string
  Hash: TMD5Hash;      // Result
begin
  // Define source data
  S := 'Привет';

  // Define exact representation as bytes
  // We use UTF-8 in this example
  UTF8Str := UTF8Encode(S);
  // (you can also just do UTF8Str := S; in Delphi 2009 and up)

  // Calculate hash from bytes
  Hash := MD5Hash(UTF8Str);

  // Show hash to a human
  Label1.Caption := HexEncodeToString(@Hash, SizeOf(Hash));
  // Should display '8A669E9418750C81AB90AE159A8EC410'
end;

2. Calculate hash of a file:
uses
  EEncoding,      // for HexEncodeToString
  EHash,          // for SHA256Hash
  ECompatibility; // for FileToString
  
procedure TForm1.Button1Click(Sender: TObject);
var
  Content: RawByteString; // File's bytes
  Hash: TSHA256Hash;      // Result
begin
  // Loads entire file into memory 
  Content := FileToString(ParamStr(0));
  // Content will be something like 'MZP'#0#2#0#0#0...

  // Calculate hash from bytes
  Hash := SHA256Hash(Content);
  Finalize(Content); // optional

  // Show hash to a human
  Label1.Caption := HexEncodeToString(@Hash, SizeOf(Hash));
  // Should be something like 'FCF52FDC753E3797FE5EE4B5A7680E656D044D6BF7D97C408F0F7874492E43C2'
end;

3. Calculate hash of a string in an arbitrary encoding:
uses
  EEncoding,      // for HexEncodeToString (also for TEncoding for older Delphi)
  EHash;          // for CRC32Hash

procedure TForm21.Button1Click(Sender: TObject);
var
  S: String;           // Source string
  Encoding: TEncoding; // Encoding to encode the string
  Content: TBytes;     // String's bytes
  Hash: TCRC32Hash;    // Result
begin
  // Define source data
  S := 'Привет';

  // Define the encoding
  Encoding := TEncoding.GetEncoding(866);
  // You can also do:
  // Encoding := TEncoding.UTF8;
  // Encoding := TEncoding.Unicode;
  // Encoding := TEncoding.ANSI;
  try

    // Convert string (characters) to bytes
    Content := Encoding.GetBytes(S);

  finally
    FreeAndNil(Encoding);
  end;

  // Calculate hash from bytes
  Hash := CRC32Hash(Pointer(Content), Length(Content));
  Finalize(Content); // optional

  // Show hash to a human
  Label1.Caption := HexEncodeToString(@Hash, SizeOf(Hash));
  // Should be '6DB3A7B9'
  // You can also do IntToStr(Hash) - which will be 3114775405
end;

4. Check hash in PHP:
uses
  EEncoding, // for HexEncodeToString
  EHash,     // for MD5Hash
  ECore;     // for ShellExec

procedure TForm1.Button1Click(Sender: TObject);
var
  S: String;           // Source string
  UTF8Str: UTF8String; // String's bytes
  Hash: TMD5Hash;      // Hash (bytes)
  HashStr: String;     // Hash (text)
begin
  // Define source data
  S := 'Привет';

  // Define exact representation as bytes
  // We use UTF-8 in this example
  UTF8Str := UTF8Encode(S);
  // (you can also just do UTF8Str := S; in Delphi 2009 and up)

  // Calculate hash from bytes
  Hash := MD5Hash(UTF8Str);

  // Convert bytes to text
  HashStr := HexEncodeToString(@Hash, SizeOf(Hash));
  // Will be '8A669E9418750C81AB90AE159A8EC410'
  
  // Pass hash as text into PHP script
  ShellExec(Format('http://localhost/test.php?hash=%s', [HashStr]));
end;
<?php

  // Source data (stored as UTF-8)
  $Source = 'Привет';
  
  // Calculate hash of source (UTF-8 encoded) data
  // (the function will return text, not bytes)
  $Hash = md5($Source);

  // Read script's parameter
  $HashArg = $_GET['hash'];

  // Ensure both passed and calculated hashes match by comparing string representation
  if (strtolower($Hash) == strtolower($HashArg)) {
  // or you can do this starting with PHP 5.6:
  // if (hash_equals($HashArg, $Hash)) {
  
    echo('OK'); // we should get there, 
                // e.g. source strings match
                // 'Привет' in Delphi = 'Привет' in PHP
  } else {
    echo('FAIL');
  }

5. Storing user credentials in a database:
uses
  EHash,     // for SHA256HMAC
  EEncrypt,  // for InitSalt
  EEncoding; // for RAWToString and HexEncodeToString/HexDecodeFromString

procedure TForm1.Button1Click(Sender: TObject);
var
  UserName: String;               // User's login (text)
  UserPassword: String;           // User's password (text)
  UserPasswordRAW: RawByteString; // User's password (bytes)
  Salt: TSalt;                    // Salt (bytes)
  SaltStr: String;                // Salt (text)
  Hash: TSHA256Hash;              // Password's hash (bytes)
  Hash2: TSHA256Hash;             // Hash from database (bytes)
  HashStr: String;                // Password's hash (text)
begin
  // Step 1. Create a new account

  // Obtain user's credentials somehow:
  UserName     := InputBox('Sign in', 'Enter the login:', '');
  UserPassword := InputBox('Sign in', 'Enter the password:', '');

  // Create random bytes to be used as salt
  Salt := InitSalt;

  // Convert password (text) to bytes
  UserPasswordRAW := UTF8Encode(UserPassword);

  // Calculate hash from password (bytes) and salt (bytes) via HMAC
  Hash := SHA256HMAC(@Salt, SizeOf(Salt), Pointer(UserPasswordRAW), Length(UserPasswordRAW));

  // Convert bytes to text
  SaltStr := HexEncodeToString(@Salt, SizeOf(Salt));
  HashStr := HexEncodeToString(@Hash, SizeOf(Hash));

  // Insert a new record into database
  // This is a pseudo-code
  InsertIntoDBTable('users', ['login', 'salt', 'password'], [UserName, SaltStr, HashStr]);
  // Here:
  // 'users'    - table's name
  // 'login'    - string field of arbitrary length
  // 'salt'     - string field of 32 characters or binary field of 16 bytes
  // 'password' - string field of 64 characters or binary field of 32 bytes



  // Step 2. Authenticate a user
  
  // Obtain user's credentials somehow:
  UserName     := InputBox('Log in', 'Enter the login:', '');
  UserPassword := InputBox('Log in', 'Enter the password:', '');

  // Search database for a user with the provided login
  // This is a pseudo-code
  // Real code should use "arguments" 
  Query := Format('SELECT salt, password FROM users WHERE login = ''%s'' LIMIT 1', [UserName]);
  Values := DBQuery(Query);

  // If there is no DB entry - then the login is not correct
  if Length(Values) = 0 then
  begin
    ShowMessage('Invalid login');
    Exit;
  end;

  // Convert salt and hash from text to bytes
  SaltStr := Values[0]; // 'salt' field from SELECT
  HashStr := Values[1]; // 'password' field from SELECT

  Assert(HexCalcDecodedSize(Length(SaltStr)) = SizeOf(Salt));
  HexDecodeFromString(SaltStr, @Salt);

  Assert(HexCalcDecodedSize(Length(HashStr)) = SizeOf(Hash2));
  HexDecodeFromString(HashStr, @Hash2);

  // Calculate hash in the same way as above
  UserPasswordRAW := UTF8Encode(UserPassword);
  Hash := SHA256HMAC(@Salt, SizeOf(Salt), Pointer(UserPasswordRAW), Length(UserPasswordRAW));

  // Now we have:
  // Hash  - hash (bytes) from the entered password
  // Hash2 - hash (bytes) from the database
  // If both hashes are equal - then password is correct

  // Ensure correct password by comparing hashes
  if CompareMem(@Hash, @Hash2, SizeOf(Hash)) then
    ShowMessage('OK')
  else
    ShowMessage('Invalid password');
end;
<?php

  // Step 1. Create a new account

  // Obtain user's credentials somehow:
  $UserName     = $_GET['login'];
  $UserPassword = $_GET['password'];

  // Create random bytes to be used as salt
  $Salt = random_bytes(16);

  // Calculate hash from password (UTF-8 encoded) and salt (raw bytes) via HMAC
  $HashStr = hash_hmac('sha256', $Salt, $UserPassword);

  // Convert bytes to text
  $SaltStr = bin2hex($Salt);

  // Insert a new record into database
  // This is a pseudo-code
  InsertIntoDBTable('users', ['login', 'salt', 'password'], [$UserName, $SaltStr, $HashStr]);
  // Here:
  // 'users'    - table's name
  // 'login'    - string field of arbitrary length
  // 'salt'     - string field of 32 characters or binary field of 16 bytes
  // 'password' - string field of 64 characters or binary field of 32 bytes



  // Step 2. Authenticate a user
  
  // Obtain user's credentials somehow:
  $UserName     = $_GET['login'];
  $UserPassword = $_GET['password'];

  // Search database for a user with the provided login
  // This is a pseudo-code
  // Real code should use "arguments" 
  $Query = 'SELECT salt, password FROM users WHERE login = \'' . $UserName . '\' LIMIT 1';
  $Values = DBQuery($Query);

  // If there is no DB entry - then the login is not correct
  if (empty($Values)) {
    echo('Invalid login'); 
    die;
  }	

  // Convert salt and hash from text to bytes
  $SaltStr  = Values['salt'];     // 'salt' field from SELECT
  $HashStr2 = Values['password']; // 'password' field from SELECT

  $Salt = hex2bin($SaltStr);

  // Calculate hash in the same way as above
  $HashStr = hash_hmac('sha256', $Salt, $UserPassword);

  // Now we have:
  // $HashStr  - hash (text) from the entered password
  // $HashStr2 - hash (text) from the database
  // If both hashes are equal - then password is correct

  // Ensure correct password by comparing hashes (as text)
  if (strtolower($HashStr) == strtolower($HashStr2)) {
  // or you can do this starting with PHP 5.6:
  // if (hash_equals($HashStr2, $HashStr)) {
  
    echo('OK'); // should get there, 
                // e.g. password is correct
  } else {
    echo('Invalid password');
  }


Encryption

EurekaLog has the following encryption functions:
  • In-process encryption (for example, to protect passwords)
  • Cross-process encryption (for example, to protect stored data within user account, or even within whole PC)
  • TEA
  • Twofish
  • RSA

Similar to hash functions, encryption functions also accepts pointer+size or RawByteString. However, encryption functions have to return data of arbitrary size too. That is why you can also use the TEncryptBuffer record, which just combines pointer and its size into a single argument.

In-process encryption

Sometimes an application has to operate on "secret" information (such as user's password). It is necessary to store such sensitive information in an encrypted form to reduce the leakage risks. You can read more about this practice in MSDN or (highly recommended) book. EurekaLog offers the following functions to protect sensitive information within a process:
The MemProtect function encrypts the specified memory block within a process in a such a way that it can be decrypted back only from the same process. The MemUnprotect function decrypts info back. The SecureFree function could be used to securely dispose of almost anything. This function will wipe (erase) memory before freeing it.

For example:
uses
  EEncrypt; // for MemProtect/MemUnprotect and SecureFree
  
procedure TForm1.Button1Click(Sender: TObject);
var
  UserPassword: String;
  StoredPassword: TEncryptBuffer;
  ClearText: TEncryptBuffer;
begin
  // Prepare all buffers
  FillChar(StoredPassword, SizeOf(StoredPassword), 0);
  FillChar(ClearText, SizeOf(ClearText), 0);

  // Obtain some confidential info
  UserPassword := InputBox('Query', 'Enter the password:', '');
  try

    // Encrypt info immediately
    ClearText.pbData := Pointer(UserPassword);
    ClearText.cbData := Length(UserPassword) * SizeOf(Char);
    MemProtect(ClearText, StoredPassword);

  finally
    // Wipe the confidential info
    SecureFree(UserPassword);
    // No need to dispose ClearText,
    // because we did not allocate memory for it
  end;

  // ...

  // Now we have StoredPassword - encrypted confidential info
  // We would need to decrypt it each time we want to use it
  // Don't forget to wipe unencrypted info once you have finished using it

  // ...

  // Decrypt the info:
  MemUnprotect(StoredPassword, ClearText);
  try

    // Use confidential info somehow
    Hash := MD5Hash(ClearText.pbData, ClearText.cbData);

  finally
    // Wipe unencrypted info after use
    SecureFree(ClearText);

    // You can also wipe derived info 
    SecureFree(Hash);
  end;

  // ...

  // Clear (encrypted) confidential info when you no longer need it
  SecureFree(StoredPassword);
end;

Cross-process encryption

Sometimes confidential information has to be stored somewhere. For example, a "Remember me" feature could write login/password pair into registry. The MemProtect/MemUnprotect function will not help you in such cases, because those functions will not work between/across processes (e.g. app's restart means creating a new process). Therefore, EurekaLog offers similar functions: DataProtect and DataUnprotect. For example:
uses
  EEncrypt,  // for DataProtect/DataUnprotect and SecureFree
  EConfig,   // for RegKeyWrite/RegKeyRead
  EEncoding; // for Base64EncodeString/Base64DecodeString

procedure TForm1.Button1Click(Sender: TObject);
var
  UserPassword: String;
  StoredPassword: RawByteString;
  ClearText: RawByteString;
begin
  // Obtain some confidential info
  UserPassword := InputBox('Query', 'Enter the password:', '');
  try

    // Convert to RawByteString for convenience
    ClearText := UTF8Encode(UserPassword);

    // Wipe the original form
    SecureFree(UserPassword);

    // Encrypt/protect the sensitive info
    StoredPassword := DataProtect(ClearText);
    // or:
    // StoredPassword := DataProtect(ClearText, True);
    // if you want to use HKEY_LOCAL_MACHINE below

    // Wipe the confidential info
    SecureFree(ClearText);

    // Convert encrypted bytes to text
    UserPassword := Base64EncodeString(StoredPassword);

    // Store encrypted confidential info into registry
    RegKeyWrite(HKEY_CURRENT_USER, '\SOFTWARE\YourApp', 'SavedPassword', UserPassword);

    // Optional
    SecureFree(UserPassword);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(UserPassword);
    SecureFree(StoredPassword);
    SecureFree(ClearText);
  end;

  // ...

  // Now we have encrypted sensitive info stored in the registry
  // We need to read and decrypt it each time we want to use it
  // Don't forget to wipe it after usage

  // ...

  // Read (encrypted) confidential info from the registry
  UserPassword := RegKeyRead(HKEY_CURRENT_USER, '\SOFTWARE\YourApp', 'SavedPassword', '');
  try

    // Convert text back to bytes
    StoredPassword := Base64DecodeString(UserPassword);

    // Optional
    SecureFree(UserPassword);

    // Decrypt confidential info
    ClearText := MemUnprotect(StoredPassword);

    // Optional
    SecureFree(StoredPassword);

    // Need to convert back to text
    UserPassword := UTF8ToString(ClearText);

    // Wipe the unsecured data
    SecureFree(ClearText);

    // Use sensitive info somehow
    Hash := MD5Hash(Pointer(UserPassword), Length(UserPassword) * SizeOf(Char));

    // Wipe once finished
    SecureFree(UserPassword);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(UserPassword);
    SecureFree(StoredPassword);
    SecureFree(ClearText);
  end;
end;

Symmetric encryption

EurekaLog supports TEA and Twofish symmetric ciphers. Both ciphers are not patented and can be used in any app. TEA is used in a wide variety of hardware due to its extremely low memory requirements and ease of implementation. Twofish is a robust general purpose symmetric encryption algorithm.

Note that encrypted data can be larger than the original data - since symmetric encryption algorithms often operate in blocks of data ("chunks"). A distinctive feature of TEA is that the encrypted data will be equal in size to the original data, so the TEA encryption/decryption functions have an overloaded option for in-place operations, i.e. without memory reallocation. For the Twofish algorithm, the data size must be a multiple of the block size (16 bytes) - otherwise the data will be padded with the PKCS#5 algorithm to a minimum required size.

EurekaLog offers algEncrypt/algDecrypt functions for both ciphers. All functions accepts key and source data. The difference is that Twofish functions allow you to additionally specify an optional IV (initialization vector). Initialization vector is just a set of random bytes (which you can create by calling the TwofishInitIV function), which ensures that two equal source data will look different after encryption, e.g. it is something like salt for hash. You don't need to store initialization vector securely, when used - it should be stored/transmitted together with encrypted data.

Both cipers use binary keys (e.g. bytes) for encryption. Because cipers are "symmetric", this means that decryption key must be the same as encryption key. Obviosly, TEA key and Twofish key have different (but fixed) size. As a rule, encryption keys are not random, but are obtained from passwords entered by a user. To convert an arbitrary password into a key of a fixed length, the algDeriveKey functions are used, which accepts a block of data of an arbitrary size. Key is derived from a password by a simple call to the hash function with an appropriate size result. For example, for TEA it will be MD5, for Twofish it will be SHA-256. The derive functions also have an overload that accepts the password as a string with an optional salt. In this case, the password is converted to UTF-8 representation, and the hash is taken from the string 'salt' + 'UTF-8 password'. In addition, there is another version of derive functions: algDeriveKeyHMAC function, which uses the HMAC algorithm to combine salt and password. In general, if you plan to use salt - we recommend using the algDeriveKeyHMAC functions.

If the encryption keys are obtained in some other way (e.g. not from a password) - you can also exchange keys directly, without "deriving" them from passwords. Just treat the key as a fixed size record/bytes array. The only pitfall is that EurekaLog uses optimization with Twofish: the key is not used directly, but is first converted into an intermediate version, which allows optimizing encryption and decryption operations. The original key is named TTwofishRAWKey and the optimized version is TTwofishKey.

For example:

1. Encryption/decryption using a password:
uses
  EEncrypt; // for InitSalt, TEADeriveKey, TEAEncrypt/TEADecrypt, SecureFree

procedure TForm1.Button1Click(Sender: TObject);
var
  Salt: TSalt;                   // Salt (random bytes)
  Key: TTEAKey;                  // Key (derived from password)
  Source: String;                // Source text
  SourceBytes: RawByteString;    // Source text (bytes)
  EncryptedBytes: RawByteString; // Encrypted text
begin
  // Prepare all buffers
  FillChar(Salt, SizeOf(Salt), 0);
  FillChar(Key, SizeOf(Key), 0);
  
  // Step 1: Encryption
  try
  
    // Define source data
    Source := 'Привет';

    // Define exact representation as bytes
    // We use UTF-8 in this example
    SourceBytes := UTF8Encode(Source);

    // No need source data anywore - wipe it
    SecureFree(Source);

    // Generate random bytes to be used as salt
    Salt := InitSalt;

    // Derive key from password and salt
    Key := TEADeriveKeyHMAC('a super secret password', Salt);

    // Encrypt source data
    EncryptedBytes := TEAEncrypt(Key, SourceBytes);

    // Wipe everything that we no longer need
    SecureFree(Key);
    SecureFree(SourceBytes);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(Source);
    SecureFree(SourceBytes);
    SecureFree(Key);
  end;

  // How we have:
  // Salt - salt to derive key from password
  // EncryptedBytes - encrypted data (arbitrary size)
  // Both of those should be transferred to decryption side

  // Step 2: Decryption
  try
    // Derive key from password and salt (salt should be passed to us)
    Key := TEADeriveKeyHMAC('a super secret password', Salt);
    // Now Key must match encryption key exactly

    // Decrypt encrypted data
    SourceBytes := TEADecrypt(Key, EncryptedBytes);

    // Wipe key
    SecureFree(Key);

    // Optional
    SecureFree(EncryptedBytes);

    // Convert data back to text
    Source := UTF8ToString(SourceBytes);

    // Wipe unneeded data
    SecureFree(SourceBytes);

    // Use source data somehow
    ShowMessage(Source);

    // Wipe source data after usage
    SecureFree(Source);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(Source);
    SecureFree(SourceBytes);
    SecureFree(EncryptedBytes);
    SecureFree(Key);
    SecureFree(Salt);
  end;
end;

2. Exchange encrypted data between Delphi and PHP:
uses
  EEncrypt,  // for all Twofish functions and SecureFree
  EEncoding, // for Base64EncodeToString
  ECore;     // for ShellExec
  
procedure TForm1.Button1Click(Sender: TObject);
const
  // Secret key known to both parties
  // It must match key in PHP-script exactly
  // Basically, this is just random bytes, which you can generate via TwofishInitSessionKeyRAW
  // Obviosly, if you are going to use this example - you MUST replace this constant
  // This is just an example. A real app may store this key somewhere
  SecretKey: TTwofishRAWKey =
    (160,  22, 228,   9,  73, 192, 173, 149,
     154,  19, 115, 215,  74,  36,  20, 202,
     178,  26, 103 , 47,  51,   4, 144,  20,
     73,  153,  49, 160, 192,  25,  20, 114);
var
  Key: TTwofishKey;             // Optimized key (created from constant above)
  IV: TTwofishInitVector;       // Initialization vector (bytes)
  Text: String;                 // Source data (text)
  TextRAW: RawByteString;       // Source data (bytes)
  EncryptedText: RawByteString; // Encrypted data (bytes)
  EncodedIV: String;            // Initialization vector (text)
  EncodedText: String;          // Encrypted data (text)
  URL: String;                  // URL to call PHP-script
  ReplyRAW: RawByteString;      // Reply from PHP-script (bytes)
  Reply: String;                // Reply from PHP-script (text)
begin
  // Prepare buffers
  FillChar(Key, SizeOf(Key), 0);
  FillChar(IV, SizeOf(IV), 0);
  try

    // Obtain source data somehow
    Text := 'Привет!';

    // Convert text to bytes. Use UTF-8 in this example
    TextRAW := UTF8Encode(Text);

    // No longer needed
    SecureFree(Text);

    // Prepare (optimize) the key
    Key := TwofishInitKey(SecretKey);

    // Generate random bytes to be used as initialization vector
    IV := TwofishInitIV;

    // Encrypt source data
    // This will use CBC mode, because initialization vector is used
    EncryptedText := TwofishEncrypt(Key, TextRAW, @IV);

    // Wipe source text
    SecureFree(TextRAW);

    // Convert bytes to text
    EncodedIV := Base64EncodeToString(@IV, SizeOf(IV));
    EncodedText := Base64EncodeString(EncryptedText);

    // Optional
    SecureFree(EncryptedText);

    // Crearte URL to call PHP-script
    // URLEncode is required to escape the '+' character
    // If you are going to use HEX-encoding instead of Base64 - then URLEncode is not required
    URL := Format('http://localhost/test.php?iv=%s&text=%s', [URLEncode(EncodedIV), URLEncode(EncodedText)]);

    // Optional
    SecureFree(EncodedIV);
    SecureFree(EncodedText);

    // Call PHP-script
    if not InitWebTools then
      RaiseLastOSError;
    try
      ReplyRAW := InternetGet(URL, [], []);
    finally
      DoneWebTools;
    end;

    // PHP-script did not returned anything?
    if ReplyRAW = '' then
      Abort;

    // Optional
    SecureFree(URL);

    // Convert text to bytes
    ReplyRAW := Base64DecodeString(Trim(String(ReplyRAW)));

    // Decrypt data
    TextRAW := TwofishDecrypt(Key, ReplyRAW, @IV);

    // No longer needed
    SecureFree(Key);
    SecureFree(IV);

    // Optional
    SecureFree(ReplyRAW);

    // Convert bytes back to text
    Text := UTF8ToString(TextRAW);

    // No longer needed
    SecureFree(TextRAW);

    // Use reply from PHP-script
    ShowMessage(Text);
    // Should show:
    // 'Hello from PHP: Привет'

    // Wipe the reply after use
    SecureFree(Text);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(Text);
    SecureFree(TextRAW);
    SecureFree(Key);
    SecureFree(IV);
    SecureFree(EncryptedText);
    SecureFree(EncodedIV);
    SecureFree(EncodedText);
    SecureFree(URL);
    SecureFree(ReplyRAW);
    SecureFree(Reply);
  end;
end;
<?php

// The functions below are required, because MCrypt use zero-padding instead of PKCS#5
// While OpenSSL does support PKCS#5, but it does not support Twofish

// PKCS#5 padding
function pkcs5_pad($text, $blocksize = 16) { 
  $pad = $blocksize - (strlen($text) % $blocksize); 
  return $text . str_repeat(chr($pad), $pad); 
} 

// PKCS#5 removal (unpadding)
function pkcs5_unpad($text) {
  $pad = ord($text{strlen($text)-1});
  if ($pad > strlen($text)) {
    return false;
  }  
  if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
    return false;
  }  
  return substr($text, 0, -1 * $pad);
}

  // Secret key known to both parties
  // It must match key in Delphi exactly
  // Basically, this is just random bytes, which you can generate via random_bytes(32)
  // Obviosly, if you are going to use this example - you MUST replace this constant
  // This is just an example. A real app may store this key somewhere
  $Key = pack('C*', 
     160,  22, 228,   9,  73, 192, 173, 149,
     154,  19, 115, 215,  74,  36,  20, 202,
     178,  26, 103 , 47,  51,   4, 144,  20,
     73,  153,  49, 160, 192,  25,  20, 114);

  // Read passed data (initialization vector and encrypted data)
  $EncodedIV   = $_GET['iv'];
  $EncodedText = $_GET['text'];
 
  // Convert text to bytes
  $IV            = base64_decode($EncodedIV);
  $EncryptedText = base64_decode($EncodedText);
  
  // Decrypt data (you must use MCRYPT_TWOFISH256 instead of MCRYPT_TWOFISH in some versions of PHP)
  $Text = mcrypt_decrypt(MCRYPT_TWOFISH, $Key, $EncryptedText, MCRYPT_MODE_CBC, $IV);
  
  // Trim data to its real size
  $Text = pkcs5_unpad($Text);

  // Do something with source data from Delphi
  $Text = 'Hello from PHP: ' . $Text;
  
  // Pad data up to chunk border
  $Text = pkcs5_pad($Text);
  
  // Encrypt source data (you must use MCRYPT_TWOFISH256 instead of MCRYPT_TWOFISH in some versions of PHP)
  $EncryptedText = mcrypt_encrypt(MCRYPT_TWOFISH, $Key, $Text, MCRYPT_MODE_CBC, $IV);
  
  // Convert bytes to text
  $EncodedText = base64_encode($EncryptedText);
  
  // Send encrypted data back to Delphi
  echo($EncodedText);

3. Storing file encrypted:
uses
  EEncrypt,       // for all Twofish functions and SecureFree
  EEncoding,      // for HexEncodeToString/HexDecodeString
  EConfig,        // for RegKeyWrite/RegKeyRead
  ECompatibility; // for FileToString/StringToFile

procedure TForm1.Button1Click(Sender: TObject);
var
  RAWKey: TTwofishRAWKey;       // Encryption/decryption (session) key (RAW bytes)
  Key: TTwofishKey;             // Encryption/decryption (session) key (optimized)
  IV: TTwofishInitVector;       // Initialization vector
  Content: RawByteString;       // Source file (bytes)
  EncryptedData: RawByteString; // Encrypted file
  DataClear: RawByteString;     // To encrypt encryption/session key
  DataEncrypted: RawByteString; // To encrypt encryption/session key
  DataStr: String;              // Encrypted encryption/session key (text)
begin
  // Prepare all buffers
  FillChar(RAWKey, SizeOf(RAWKey), 0);
  FillChar(Key, SizeOf(Key), 0);
  FillChar(IV, SizeOf(IV), 0);

  // Step 1: encrypt file on disk with random key
  try
  
    // Create random key (random bytes)
    RAWKey := TwofishInitSessionKeyRAW;

    // Optimize it for further use
    Key := TwofishInitKey(RAWKey);

    // Load whole file into a string
    Content := FileToString(ParamStr(0));
    // Content will be like 'MZP'#0#2#0#0#0...

    // Use random bytes as initialization vector
    IV := TwofishInitIV;

    // Encrypt data (e.g. file)
    EncryptedData := TwofishEncrypt(Key, Content, @IV);

    // Wipe not needed data
    SecureFree(Content);
    SecureFree(Key);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(Content);
    SecureFree(Key);
  end;

  // Now we have:
  // RAWKey - (secret) session key, which was used to encrypt the file
  // IV - (open) initialization vector
  // EncryptedData - encrypted file

  // Step 2: protect session key
  try

    // Convert to RawByteString for convenience
    DataClear := RAWToString(@RAWKey, SizeOf(RAWKey));

    // Wipe key (no longer needed)
    SecureFree(RAWKey);

    // Encrypt encryption key
    DataEncrypted := DataProtect(DataClear);

    // Wipe unneccessary data
    SecureFree(DataClear);

    // Convert bytes to text
    DataStr := HexEncodeToString(Pointer(DataEncrypted), Length(DataEncrypted));

    // Optional
    SecureFree(DataEncrypted);

    // Store encrypted session key into registry
    // We can safely do that, because this key can be decrypted back by the same user account only
    RegKeyWrite(HKEY_CURRENT_USER, '\SOFTWARE\YourApp', 'EncryptionKey', DataStr);

    // Optional
    SecureFree(DataStr);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(RAWKey);
    SecureFree(DataClear);
    SecureFree(DataEncrypted);
    SecureFree(DataStr);
  end;

  // Now we have:
  // Encrypted (secret) session (encryption) key in the registry
  // IV - (open) initialization vector
  // EncryptedData - encrypted file
  // So, if we want to save encrypted file to disk - we need to save both IV and EncryptedData
  // Only current user account would be able to decrypt data back

  // Step 3: decrypt session key
  try

    // Read stored session key (as text)
    DataStr := RegKeyRead(HKEY_CURRENT_USER, '\SOFTWARE\YourApp', 'EncryptionKey', '');

    // Convert text to bytes
    DataEncrypted := HexDecodeString(DataStr);

    // Optional
    SecureFree(DataStr);

    // Decrypt session key
    DataClear := DataUnprotect(DataEncrypted);

    // Optional
    SecureFree(DataEncrypted);

    // Prepare session key (bytes)
    Assert(Length(DataClear) = SizeOf(RAWKey));
    RAWFromString(DataClear, @RAWKey);

    // Wipe it's copy
    SecureFree(DataClear);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(DataStr);
    SecureFree(DataEncrypted);
    SecureFree(DataClear);
  end;

  // Now we have:
  // RAWKey - (secret) key, which was used to encrypt the file
  // IV - (open) initialization vector
  // EncryptedData - encrypted file

  // Step 4: decrypt the file
  try

    // Optimize the key for usage
    Key := TwofishInitKey(RAWKey);

    // Wipe source (no longer needed)
    SecureFree(RAWKey);

    // Decrypt the data (file)
    // Both Key and IV must be exactly the same as in encryption at step 1 above
    Content := TwofishDecrypt(Key, EncryptedData, @IV);

    // Wipe the key (no longer needed)
    SecureFree(Key);

    // Optional
    SecureFree(EncryptedData);

    // Store unencrypted data back to file
    StringToFile(ParamStr(0) + '.copy', Content);
    // Now the Project1.exe.copy file must be exact copy of Project1.exe

    // Wipe unnecessary data
    SecureFree(Content);

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(RAWKey);
    SecureFree(Key);
    SecureFree(EncryptedData);
    SecureFree(Content);
  end;
end;

Asymmetric Cryptography

For asymmetric encryption - EurekaLog supports RSA. The algorithm is not proprietary and can be freely used in any app. The asymmetry of the algorithm means that it uses two different keys: one key is used for encryption, the other key is used for decryption. One of the keys is kept secret, it is called "secret" or "private" key, and the other can be published - it is called "public" or "open" key. Whatever is encrypted with the public key can only be decrypted back with the matched private key and vice versa.

Key Management

EurekaLog stores RSA keys into the TRSAKey record, which have two fields: PublicKey to store public key and PrivateKey to store private key. You can create a brand new pair of keys by calling the RSAGenKey function (which will take a while - say, 5-15 seconds). As a rule, keys are not generated in apps, but ready-made (pre-generated) keys are loaded. EurekaLog offers the RSALoad/SavePublic/PrivateKey functions to load and save keys, for example: RSALoadPrivateKey. EurekaLog supports few formats to export/import the keys, which are described by the TRSAExport/TRSAImport type:
  • rsBLOB - this is a binary representation of a key with a header. The PUBLICKEYBLOB/PRIVATEKEYBLOB record from Microsoft is used as the header. Little-Endian.
  • rsDER - this is a binary representation of a key, encoded into ASN.1 container without any headers (so called PKCS#1), Big-Endian. If you attempt to load ASN.1 container with a header (PKCS#8) - it will be silently ignored. It matches the RSA_CSP_PUBLICKEYBLOB/PKCS_RSA_PRIVATE_KEY in CryptoAPI. As a rule, this format is used to save into .der files.
  • rsPEM - this is a textual representation of a key. Basically, it is the same rsDER, but encoded into Base64. As a rule, this format is used to save into .pem files. .key, .cert, .cer, or .crt are also used. PKCS#1 compliant files use headers like -----BEGIN/END RSA PRIVATE KEY-----, -----BEGIN/END RSA PUBLIC KEY-----, while PKCS#8 complient files use headers like -----BEGIN/END PRIVATE KEY-----, -----BEGIN/END PUBLIC KEY-----.
EurekaLog does not support encrypted PKCS#1/PKCS#8, nor PKCS#7 and PKCS#12. PKCS#8 is supported by EurekaLog, but only when importing (see below).

For example, the very same key can be exported like the following:
  • rsBLOB (the PUBLICKEYBLOB/PRIVATEKEYBLOB header + little-endian, key $25 $17 $B4 $A0 ... $96 $B9 $9C $E7 starts from byte $15/21 and ends at the file's end):
    rsBLOB
  • rsDER (ASN.1-container PKCS#1 + big-endian, e.g. the same key $E7 $9C $B9 $96 ... $A0 $B4 $17 $25 starts with byte 10 and ends at byte 6 from file's end):
    rsPEM
  • rsPEM (Base64-encoded ASN.1-container PKCS#1):
    -----BEGIN RSA PUBLIC KEY-----
    MIICCgKCAgEA55y5ll1KryRC7umxntWX7t3zOP3qUVxQo7gin3sA1dePyzLxTxtE
    47R+/sqkgFygXdlBqnmjbwu60kU2Zd7k7QFGhZWqfPcAYI3xd660vUPnmXK7n2R1
    3AtF2BW/5MqIH7D3ddjLCt5CoUn6KRZSuz+pySDpuquKerRB5Gq/0WjUG2IIcQXU
    Z1i4qMicPhbOJH76rFPgRngBuvJtS0UCBKx4YOlK0q1JUUJ1leSGp2gAjYGrD7fN
    SOU8r70a97NDu4UblmsS9zW29OHAEF7jNFsVNVBU78P/XZ4hmL41gaPRGws3HXfA
    vGbVattUzHTHsHMJeRLoiPAgak3TqAM2px7qOcNNN8FB91XbnxzvPARfDrBMbpc4
    OcWmDSMuc1RGI/mQCIlGRvA2nhD7Dfu3L5sxnrjjOC+LLpIVsGe5+cs1ZkfD7kII
    AzV/MXXNlx366n/Z1+u97VocmvHcqVCl/s9AMqdXflzAYD+9p7bXhJdP9XfOXf9z
    zCyPBK/Iyk+B4lRR9cmuBW7FAq1JM3PZWZ2mEx0fgrL8M0w5cf2Ts84XtNIEFDa6
    MFOe48sJfDIiPPw4ePohSuYpAY71Du2cQe87VQAf/caclWsrFplItilN93Xx5kQW
    5S16HHLc7A+EKEaNBnUsNl+n0/99jjfHA9PAqxFVaVT68X9eSKC0FyUCAwEAAQ==
    -----END RSA PUBLIC KEY-----

Creating and exporting a new key pair:
  1. Delphi (PKCS#1):
    uses
      EEncrypt; // for all RSA functions
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      Key: TRSAKey;
    begin
      // Create a new key pair (takes few seconds)
      Key := RSAGenKey;
      try
      
        // Export both keys into all formats (only as an example)
        RSASavePublicKey(Key, 'C:\Documents\public.blob', rsBLOB);
        RSASavePublicKey(Key, 'C:\Documents\public.der',  rsDER);
        RSASavePublicKey(Key, 'C:\Documents\public.pem',  rsPEM);
    
        RSASavePrivateKey(Key, 'C:\Documents\private.blob', rsBLOB);
        RSASavePrivateKey(Key, 'C:\Documents\private.der',  rsDER);
        RSASavePrivateKey(Key, 'C:\Documents\private.pem',  rsPEM);
        
      finally 
        // Wipe all keys
        SecureFree(Key);
      end;
    end;
  2. PHP (PKCS#8):
    <?php
    
    // Create a new key pair (takes few seconds)
    $config = array(
      "private_key_bits" => 4096,
      "private_key_type" => OPENSSL_KEYTYPE_RSA,
      "encrypt_key" => false
    );
    $privateKey = openssl_pkey_new($config);
    $publicKey = openssl_pkey_get_public($privateKey);
    
    // Save keys into files
    openssl_pkey_export($privateKey, $PEM, null, $config);
    file_put_contents('./private.pem', $PEM);
    file_put_contents('./public.pem', $publicKey['key']);
  3. OpenSSL (PKCS#8):
    openssl genpkey -out private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:4096
    openssl rsa -in private.pem -out public.pem -outform pem -pubout

If you want to exchange keys between Delphi/EurekaLog, Windows/WinCrypt, and PHP/OpenSSL - you need to use an ASN.1-container format (i.e. binary DER or text PEM). There is one pitfall here:
  1. Windows/WinCrypt exports/imports only keys themselfes into ASN.1-container. Such format is called PKCS#1, you can distinguish it in a text form (PEM) by the comments like -----BEGIN RSA PUBLIC KEY-----. If you open such PEM/DER file into any ASN.1 editor/viewer/decoder (or use the openssl asn1parse -in private.pem command) - you should see something like this:
    SEQUENCE (9 elem)
      INTEGER 0
      INTEGER (4096 bit) 758666102228921792910938751013886686183781000609266742264329283135491…
      INTEGER 65537
      INTEGER (4096 bit) 626038158727742962915964533848528190012037116635627896022932505790124…
      INTEGER (2048 bit) 265173868639039499374763037151257212413358779720904507462546669893261…
      INTEGER (2048 bit) 286101381754789259717271171537486143896554619163648315398099842455950…
      INTEGER (2048 bit) 241273140164272477344356926702923044634459679795497746005945617372402…
      INTEGER (2047 bit) 117977475958972484914769571551956345862709440207784850140129213152449…
      INTEGER (2047 bit) 114103066753170488160676998988478007480145103422326665392933549037964…
    8 feilds describe various components of the key (module, exponent, etc.).
  2. PHP/OpenSSL exports not just keys, but also stores additional information, such as alg ID, version, etc. Such format is called PKCS#8, you can distinguish it in a text form (PEM) by the comments like -----BEGIN PRIVATE KEY-----. If you open such PEM/DER file into any ASN.1 editor/viewer/decoder - you should see something like this:
    SEQUENCE (3 elem)
      INTEGER 0
      SEQUENCE (2 elem)
        OBJECT IDENTIFIER 1.2.840.113549.1.1.1 rsaEncryption (PKCS #1)
        NULL
      OCTET STRING (2349 byte) 30820929020100028202010098D61F2FBA3EC958DB082F286781EE7CC258ADCE2B0A…
        SEQUENCE (9 elem)
          INTEGER 0
          INTEGER (4096 bit) 758666102228921792910938751013886686183781000609266742264329283135491…
          INTEGER 65537
          INTEGER (4096 bit) 626038158727742962915964533848528190012037116635627896022932505790124…
          INTEGER (2048 bit) 265173868639039499374763037151257212413358779720904507462546669893261…
          INTEGER (2048 bit) 286101381754789259717271171537486143896554619163648315398099842455950…
          INTEGER (2048 bit) 241273140164272477344356926702923044634459679795497746005945617372402…
          INTEGER (2047 bit) 117977475958972484914769571551956345862709440207784850140129213152449…
          INTEGER (2047 bit) 114103066753170488160676998988478007480145103422326665392933549037964…
    The key is stored in the same way as in PKCS#1, but there is also an additional header added.
Different encryption libraries support various formats. If you attempt to pass key in an unsupported format - you would get errors like these:
CRYPT_E_ASN1_BADTAG (8009310B): ASN1 bad tag value met
openssl_pkey_get_private: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag

EurekaLog saves keys into PKCS#1, but it is able to load both PKCS#1 and PKCS#8. OpenSSL saves into PKCS#8, but it supports loading both PKCS#8 and PKCS#1.

IMPORTANT
OpenSSL supports PKCS#1/PKCS#8 PEM only when it has the proper comment! E.g. the PKCS#1 file must start with the -----BEGIN RSA PRIVATE KEY----- or -----BEGIN RSA PUBLIC KEY-----, while PKCS#8 file must start with the -----BEGIN PRIVATE KEY----- or -----BEGIN PUBLIC KEY-----.

P.S. You can also convert PKCS#1 (Windows/WinCrypt/EurekaLog) into PKCS#8 (PHP/OpenSSL) via:
openssl pkcs8 -topk8 -inform pem -in private.pem -outform pem -nocrypt -out private2.pem
openssl rsa -RSAPublicKey_in -in public.pem -pubout -out public2.pem
and back:
openssl rsa -inform pem -in private.pem -outform pem -out private2.pem
openssl rsa -pubin -in public.pem -RSAPublicKey_out -out public2.pem

P.P.S. Latest versions of OpenSSL supports BLOB:
Convert PEM to BLOB:
openssl rsa -inform PEM -in private.pem -outform "MS PRIVATEKEYBLOB" -out private.blob
Convert BLOB to PEM:
openssl rsa -inform "MS PRIVATEKEYBLOB" -in private.blob -outform PEM -out private.pem
Change key format to "MS PUBLICKEYBLOB" for a public key, or you can simply convert private key, then extract public key from the private key.

In summary, we recommend:
  1. Use the EurekaLog Crypto Helper tool to create a pair of new RSA keys:
    1. Run Start / Programs / EurekaLog / Tools / EurekaLog Crypto Helper
    2. Go to the Keys tab
    3. Go to the RSA tab
    4. Hit Create New button
    5. Save private and public keys into private.pem and public.pem files. This will save keys into PKCS#1.
    Alternatively you can use:
    uses
      EEncrypt; 
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      Key: TRSAKey;
    begin
      Key := RSAGenKey;
      try
        RSASavePublicKey (Key, 'C:\Documents\public.pem',  rsPEM);
        RSASavePrivateKey(Key, 'C:\Documents\private.pem', rsPEM);
      finally 
        SecureFree(Key);
      end;
    end;
  2. Import keys into Delphi (EurekaLog supports PKCS#1):
    var
      RSAKey: TRSAKey;
    begin
      // Loads PKCS#1 PEM
      RSAKey := RSALoadPrivateKey('C:\Documents\private.pem', rsPEM);
    var
      RSAKey: TRSAKey;
    begin
      // Loads PKCS#1 PEM
      RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
  3. Import keys into PHP (OpenSSL from PHP supports PKCS#1):
    <?php
    
      $PrivateKey = <<<EOD
    -----BEGIN RSA PRIVATE KEY-----
    MIIJKgIBAAKCAgEA5CBBmb8QlDVt7uqKZPW6I/GcjyzDg+7VuZd5OcQXVpglsWoa 
    ...
    aNz1gCLcqrQiTXHTVg821kYszBDySjfQGJQ3JJhf1/9XGcVjcopbWWeeNpHs5w==
    -----END RSA PRIVATE KEY-----
    EOD;
    
      // Or:
      // $PrivateKey = 'file:///var/www/private.pem';
      
      $PrivateKey = openssl_pkey_get_private($PrivateKey);
      if (!PrivateKey) {
        echo('openssl_pkey_get_private: ' . openssl_error_string());  
        die();  
      }
    <?php
    
      $PublicKey = <<<EOD
    -----BEGIN RSA PUBLIC KEY-----
    MIICCgKCAgEA5CBBmb8QlDVt7uqKZPW6I/GcjyzDg+7VuZd5OcQXVpglsWoauYvq 
    ...
    f69nl8KyfHhsqffkDeDIaA73hspgFM5bh2zGdj4n8101bjHRu8N35qECAwEAAQ==
    -----END RSA PUBLIC KEY-----
    EOD;
    
      // Or:
      // $PublicKey = 'file:///var/www/public.pem';
      
      $PublicKey = openssl_pkey_get_public($PublicKey);
      if (!PublicKey) {
        echo('openssl_pkey_get_public: ' . openssl_error_string());  
        die();  
      }
    It is important that keys must start with a correct comment. E.g. -----BEGIN RSA PRIVATE KEY----- and -----BEGIN RSA PUBLIC KEY-----.
Alternatively, you can:
  1. Download OpenSSL for Windows.
  2. Create a new key pair. The commands below will create two files (private.pem and public.pem) as PKCS#8 PEM:
    openssl genpkey -out private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:4096
    openssl rsa -in private.pem -out public.pem -outform pem -pubout
  3. Import keys in Delphi (EurekaLog supports PKCS#8):
    var
      RSAKey: TRSAKey;
    begin
      // Loads PKCS#8 PEM
      RSAKey := RSALoadPrivateKey('C:\Documents\private.pem', rsPEM);
    var
      RSAKey: TRSAKey;
    begin
      // Loads PKCS#8 PEM
      RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
  4. Import keys in PHP (OpenSSL in PHP supports PKCS#8):
    <?php
    
      $PrivateKey = <<<EOD
    -----BEGIN PRIVATE KEY-----
    MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDkIEGZvxCUNW3u
    ...
    X+VZcAA+CUNp1xYUuep0UoeqYtfzTuKvinNo3PWAItyqtCJNcdNWDzbWRizMEPJK
    N9AYlDckmF/X/1cZxWNyiltZZ542kezn
    -----END PRIVATE KEY----- 
    EOD;
    
      // Or:
      // $PrivateKey = 'file:///var/www/private.pem';
      
      $PrivateKey = openssl_pkey_get_private($PrivateKey);
      if (!PrivateKey) {
        echo('openssl_pkey_get_private: ' . openssl_error_string());  
        die();  
      }
    <?php
    
      $PublicKey = <<<EOD
    -----BEGIN PUBLIC KEY-----
    MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5CBBmb8QlDVt7uqKZPW6
    ...
    v4sIP6NMmNKN8TwtqUKxcjZJMrVhjPJWf69nl8KyfHhsqffkDeDIaA73hspgFM5b
    h2zGdj4n8101bjHRu8N35qECAwEAAQ==
    -----END PUBLIC KEY----- 
    EOD;
    
      // Or:
      // $PublicKey = 'file:///var/www/public.pem';
      
      $PublicKey = openssl_pkey_get_public($PublicKey);
      if (!PublicKey) {
        echo('openssl_pkey_get_public: ' . openssl_error_string());  
        die();  
      }
    It is important that keys must start with a correct comment. E.g. -----BEGIN PRIVATE KEY----- and -----BEGIN PUBLIC KEY-----.

Note: the openssl_pkey_get_private/openssl_pkey_get_public calls are optional. If the key variable contains the key itself in PEM format, then it can be passed directly to OpenSSL functions. In this case, the openssl_pkey_get_private/openssl_pkey_get_public functions are called only as an example and to check that the key was specified correctly.

Asymmetric Encryption

EurekaLog offers the RSAEncrypt and RSADecrypt functions, which are used in a similar way to the symmetric encryption functions above. There are only a few differences:
  1. Because asymmetric encryption uses two different keys, it is used in sender-recipient scenarios and is not used when the same person encrypts and decrypts data.
  2. Since asymmetric encryption is very slow, it is never applied to the open data itself. Instead, the open data is encrypted with any symmetric cipher with a random key (called a "session key"), and then the symmetric session key is encrypted with asymmetric encryption.
  3. The public key is used for encryption, and the private key is used for decryption. Therefore anyone can encrypt data with the recipient's public key, while only the recipient can decrypt the data. This is how secrecy is ensured.

RSAEncrypt/RSADecrypt functions work with little-endian data and use PKCS#1 Type 2 padding.

Encrypting file on a disk:
uses
  EEncrypt; // for all RSA functions and SecureFree

procedure TForm1.Button1Click(Sender: TObject);
var
  // (Random) session key to encrypt the file
  SessionKey: TTwofishKey;
  SessionKeyRAW: TTwofishRAWKey;
  // Asymmetric key to encrypt the session key
  RSAKey: TRSAKey;
  // Open data (file) to encrypt
  Data: TMemoryStream;
  // Encrypted data
  EncryptedData: TEncryptBuffer;
  // Stream to save the encrypted file
  FS: TFileStream;
begin
  // Prepare all buffers
  FillChar(SessionKey, SizeOf(SessionKey), 0);
  FillChar(SessionKeyRAW, SizeOf(SessionKeyRAW), 0);
  FillChar(RSAKey, SizeOf(RSAKey), 0);
  FillChar(EncryptedData, SizeOf(EncryptedData), 0);

  try
    // Create a new (random) session key
    SessionKeyRAW := TwofishInitSessionKeyRAW;
    SessionKey := TwofishInitKey(SessionKeyRAW);

    // FS is used twice:
    // 1). To store encrypted session key
    // 2). To store encrypted data

    FS := nil;
    try

      // Step 1: encrypt session key

      // Load public key from a file (must be prepared beforehand)
      RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
      try

        // Encrypt session key (SessionKeyRAW) into EncryptedData buffer
        EncryptedData.cbData := SizeOf(SessionKeyRAW);
        RSAEncrypt(RSAKey, @SessionKeyRAW, Pointer(EncryptedData.pbData), EncryptedData.cbData);

      finally
        // No longer need public key
        SecureFree(RSAKey);
        // No longer need source for the session key
        SecureFree(SessionKeyRAW);
      end;
      try

        // Now: EncryptedData stored encrypted session key

        // Save encrypted session key into destination file
        FS := TFileStream.Create('C:\Documents\EncryptedData.bin', fmCreate or fmShareExclusive);
        FS.WriteBuffer(EncryptedData.cbData, SizeOf(EncryptedData.cbData));
        FS.WriteBuffer(EncryptedData.pbData^, EncryptedData.cbData);

        // Don't close FS yet, we are not finished saving data...

      finally
        // Wipe encrypted session key
        SecureFree(EncryptedData);
      end;

      // Now we have SessionKey and destination file FS
      
      
      // Step 2: encrypt the file

      Data := TMemoryStream.Create;
      try

        // Load entire file into memory
        Data.LoadFromFile('C:\Documents\Text.txt');

        // Encrypt the file
        // We are not using initialization vector, so ECB mode will be used
        EncryptedData.cbData := Cardinal(Data.Size);
        TwofishEncrypt(SessionKey, Data.Memory, Pointer(EncryptedData.pbData), EncryptedData.cbData);

        // No longer needed
        SecureFree(SessionKey);

        // Now save encrypted file into destination file
        FS.WriteBuffer(EncryptedData.pbData^, EncryptedData.cbData);

      finally
        // Wipe everything
        SecureFree(Data);
        SecureFree(EncryptedData);
      end;

    finally
      // Close the file
      FreeAndNil(FS);
    end;

    // Now the C:\Documents\EncryptedData.bin file is ready
    // It contains encrypted (random) session key, as well as encrypted C:\Documents\Text.txt file
    // This file can be descrypted by someone, who has the private key
    // E.g. you can safely pass this file to the recipient by any means, including unsecured channels

  finally
    // Wipe the remaining data
    SecureFree(SessionKey);
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  // (Random) session key to decrypt the file
  SessionKey: TTwofishKey;
  // Asymmetric key to decrypt the session key
  RSAKey: TRSAKey;
  // Encrypted file
  Data: TMemoryStream;
  // Decrypted file
  DecryptedData: TEncryptBuffer;
  // Stream to load the encrypted file
  FS: TFileStream;
begin
  // Prepare all buffers
  FillChar(SessionKey, SizeOf(SessionKey), 0);
  FillChar(RSAKey, SizeOf(RSAKey), 0);
  FillChar(DecryptedData, SizeOf(DecryptedData), 0);

  // Open encrypted file
  FS := TFileStream.Create('C:\Documents\EncryptedData.bin', fmOpenRead or fmShareDenyWrite);
  try

    try
      // Step 1: decrypt the session key
    
      // Read encrypted session key from the file
      FS.ReadBuffer(DecryptedData.cbData, SizeOf(DecryptedData.cbData));
      DecryptedData.pbData := AllocMem(DecryptedData.cbData);
      try
        FS.ReadBuffer(DecryptedData.pbData^, DecryptedData.cbData);

        // Decrypt symmetric session key with private asymmetric key
        RSAKey := RSALoadPrivateKey('C:\Documents\private.pem', rsPEM);
        try
          EEncrypt.RSADecrypt(RSAKey, DecryptedData);
        finally
          SecureFree(RSAKey);
        end;

        // Initialize the session key
        Assert(DecryptedData.cbData = SizeOf(TTwofishRAWKey));
        SessionKey := TwofishInitKey(TTwofishRAWKey(Pointer(DecryptedData.pbData)^));

      finally
        // Wipe unneeded data
        SecureFree(DecryptedData);
      end;

      // Now we have symmetric SessionKey, which we can use to decrypt the rest of the file
      
      // Step 2: decrypt the file

      Data := TMemoryStream.Create;
      try
        // The remaining data in the file represents the encrypted file
        Data.CopyFrom(FS, FS.Size - FS.Position);

        // Decrypt the file
        DecryptedData.cbData := Cardinal(Data.Size);
        TwofishDecrypt(SessionKey, Data.Memory, Pointer(DecryptedData.pbData), DecryptedData.cbData);

        // No longer needed
        SecureFree(SessionKey);

        // Save decrypted file to disk
        FreeAndNil(FS);
        FS := TFileStream.Create('C:\Documents\Text2.txt', fmCreate or fmShareExclusive);
        try
          FS.WriteBuffer(DecryptedData.pbData^, DecryptedData.cbData);
        finally
          FreeAndNil(FS);
        end;

        // No longer needed
        SecureFree(DecryptedData);

        // Now Text2.txt should be exact copy of Text.txt

      finally
        SecureFree(Data);
      end;

    finally
      // Just in case (e.g. exception) - wipe everything
      SecureFree(SessionKey);
      SecureFree(DecryptedData);
    end;
  finally
    FreeAndNil(FS);
  end;
end;

Digital Signature

When a private key is used for encryption and a public key is used for decryption - it is called a "digital signature". Anyone can decrypt the data (since everyone has the public key), therefore secrecy of data is not ensured in this way. But on the other hand, if the data can be decrypted with someone's public key, we can be sure that the data was encrypted by him (because only this person has the secret private key). This way we can check authenticity of the data.

However, as a rule, data itself is not encrypted by private key. A hash is calculated from the data and the hash is then encrypted instead. EurekaLog offers RSASign (uses private key) and RSAVerify (uses public key) functions to sign and verify. EurekaLog's digital signature functions use SHA1 with EMSA-PKCS1 padding.

The resulting digital signature is an opaque array of bytes of arbitrary length. If you want to exchange digital signatures with other environments, remember that Windows/Delphi use little-endian byte order, while some other environments (e.g. .NET or PHP) use big-endian. Therefore, in some cases, the byte order of a digital signature needs to be reversed.

For example:

Requesting software license from a PHP-script:
uses
  EEncrypt,  // for all RSA functions and SecureFree
  EEncoding, // for Base64
  EJSON,     // for JSON functions
  EWebTools; // for network functions

procedure TForm1.Button1Click(Sender: TObject);
var
  // Data to be send to a PHP-script
  JSON, JSONRequest, JSONUser: IJSONValues;
  JSONText: String;
  JSONRAW: RawByteString;
  // (Random) symmetric key to encrypt the data
  SessionKey: TTwofishKey;
  SessionKeyRAW: TTwofishRAWKey;
  // Asymmetric key to encrypt the session key
  RSAKey: TRSAKey;
  // Encrypted data (bytes)
  EncryptedData: TEncryptBuffer;
  // Encrypted data (text)
  EncodedKey, EncodedData: String;
  // URL to call PHP-script
  URL: String;
  // Reply from PHP (bytes)
  ReplyRAW: RawByteString;
  // License (text)
  EncodedLicense: String;
  // Digital signature (text)
  EncodedSignature: String;
  // License (bytes)
  License: RawByteString;
  // Digital signature (bytes)
  Signature: RawByteString;
begin
  // Prepare all buffers
  FillChar(SessionKey, SizeOf(SessionKey), 0);
  FillChar(SessionKeyRAW, SizeOf(SessionKeyRAW), 0);
  FillChar(RSAKey, SizeOf(RSAKey), 0);
  FillChar(EncryptedData, SizeOf(EncryptedData), 0);

  try

    // Step 1: prepare some data to send to a PHP-script
    
    // The code below is just an example
    JSON := JSONCreate;
    JSONRequest := JSONCreate;
    JSONUser := JSONCreate;

    JSONRequest['version'] := 1;
    JSONRequest['type']  := 'license';
    JSONRequest['scope'] := 'installer';

    JSONUser['login']    := 'input-from-edit1';
    JSONUser['password'] := 'input-from-edit2';

    JSON['app']     := 'MyApp';
    JSON['version'] := GetModuleVersion(GetModuleName(HInstance));
    JSON['date']    := Now;
    JSON['request'] := JSONRequest;
    JSON['user']    := JSONUser;

    JSONText := JSON.ToString;
    Finalize(JSONUser);    // optional
    Finalize(JSONRequest); // optional
    Finalize(JSON);        // optional
    (*

    Now JSONText contains:

      {
        "app": "MyApp",
        "version": "1.0.0.0",
        "date": "2021.06.25 14:04:21",
        "request": {
          "version": 1,
          "type": "license",
          "scope": "installer"
        }
        "user": {
          "login": "input-from-edit1",
          "password": "input-from-edit2"
        }
      }

    *)
    JSONRAW := UTF8Encode(JSONText);
    SecureFree(JSONText);

    // Now JSONRAW contains bytes to send to a PHP-script


    // Step 2: encrypt request data and send encrypted data to a PHP-script

    // Generate a new random key
    SessionKeyRAW := TwofishInitSessionKeyRAW;
    SessionKey := TwofishInitKey(SessionKeyRAW);

    // Step 2a: encrypt the session key

    // Load public key from a file
    RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
    try

      // Encrypt SessionKeyRAW into EncryptedData buffer
      EncryptedData.cbData := SizeOf(SessionKeyRAW);
      RSAEncrypt(RSAKey, @SessionKeyRAW, Pointer(EncryptedData.pbData), EncryptedData.cbData);
      try

        // No longer needed
        SecureFree(RSAKey);
        SecureFree(SessionKeyRAW);

        // Convert bytes to text
        EncodedKey := Base64EncodeToString(EncryptedData.pbData, EncryptedData.cbData);

        // No longer needed
        SecureFree(EncryptedData);

      finally
        // На всякий случай (исключение) - чистим данные
        SecureFree(EncryptedData);
      end;

    finally
      // Just in case (e.g. exception) - wipe everything
      SecureFree(RSAKey);
      SecureFree(SessionKeyRAW);
    end;

    // Now:
    // - EncodedKey stores encrypted session key (as a text)
    // - SessionKey stores session key (ready to use)
    // - JSONRAW stores request's bytes

    // Step 2b: encrypt the request

    // Since we do not use initialization vector - encryption will use ECB mode
    EncryptedData.cbData := Length(JSONRAW);
    TwofishEncrypt(SessionKey, Pointer(JSONRAW), Pointer(EncryptedData.pbData), EncryptedData.cbData);

    try
      // No longer needed
      SecureFree(SessionKey);

      // Convert bytes to text
      EncodedData := Base64EncodeToString(EncryptedData.pbData, EncryptedData.cbData);

      // No longer needed
      SecureFree(EncryptedData);

      // Send both encrypted data and session key to the PHP-script
      // URLEncode is required to escape '+' in Base-64
      URL := 'http://localhost/test.php?key=' + URLEncode(EncodedKey) + '&data=' + URLEncode(EncodedData);
      if not InitWebTools then
        RaiseLastOSError;
      try
        ReplyRAW := InternetGet(URL, [], []);
      finally
        DoneWebTools;
      end;

      // PHP-script does not return anything?
      // (should not happen normally)
      if ReplyRAW = '' then
        Abort;

      // Optional
      SecureFree(URL);

      // No longer needed
      SecureFree(EncodedKey);
      SecureFree(EncodedData);

    finally
      // Just in case (e.g. exception) - wipe everything
      SecureFree(SessionKey);
      SecureFree(EncryptedData);
      SecureFree(EncodedKey);
      SecureFree(EncodedData);
    end;

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(SessionKey);
    SecureFree(SessionKeyRAW);
    SecureFree(EncodedData);
    SecureFree(EncodedKey);
    SecureFree(JSONText);
    SecureFree(JSONRAW);
  end;

  // Now we have ReplyRAW - bytes from the PHP-script

  try
    // Convert bytes to text
    JSONText := UTF8ToString(ReplyRAW);

    // Optional
    Finalize(ReplyRAW);

    // Convert JSON-text to JSON-object
    JSON := JSONCreate(JSONText);

    // Optional
    Finalize(JSONText);

    // PHP-script has returned some error?
    if JSON.IndexOf('error') >= 0 then
      raise Exception.Create(JSON['error']);

    // Extract license and digital signature (text)
    EncodedLicense   := JSON['license'];
    EncodedSignature := JSON['signature'];

    // Convert text to bytes
    License   := Base64DecodeString(EncodedLicense);
    Signature := Base64DecodeString(EncodedSignature);

    // Load public key from a file
    RSAKey := RSALoadPublicKey('C:\Documents\public.pem', rsPEM);
    try

      // Verify the digital signature
      if RSAVerify(RSAKey, Pointer(License), Length(License), Pointer(Signature), Length(Signature)) then
      begin
        // Digital signature is not broken
        // This means that License really came from our server

        // Optional
        SecureFree(RSAKey);

        // Just as an example
        // Real application would probably have License encrypted
        ShowMessage(UTF8ToString(License));
        // Will show 'This is just an example license'
        
      end
      else
        ShowMessage('Not signed');

    finally
      // Just in case (e.g. exception) - wipe everything
      SecureFree(RSAKey);
    end;

  finally
    // Just in case (e.g. exception) - wipe everything
    SecureFree(JSONText);
    SecureFree(ReplyRAW);
    SecureFree(EncodedSignature);
    SecureFree(EncodedLicense);
    SecureFree(Signature);
    SecureFree(License);
  end;
end;
<?php

// These functions are required, because MCrypt uses zero padding instead of PKCS#5
// OpenSSL supports PKCS#5, but does not support Twofish

// PKCS#5 padding
function pkcs5_pad($text, $blocksize = 16) { 
  $pad = $blocksize - (strlen($text) % $blocksize); 
  return $text . str_repeat(chr($pad), $pad); 
} 

// Trims PKCS#5 padding
function pkcs5_unpad($text) {
  $pad = ord($text{strlen($text)-1});
  if ($pad > strlen($text)) {
    return false;
  }  
  if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
    return false;
  }  
  return substr($text, 0, -1 * $pad);
}

  // Private key
  // Must be related to the public key, which was used in Delphi
  // If you are going to use this example, you need to replace this contant
  // This is just an example. Real program would store key in a file
  $PrivateKey = <<<EOD
-----BEGIN PRIVATE KEY-----
MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQDkIEGZvxCUNW3u
...
X+VZcAA+CUNp1xYUuep0UoeqYtfzTuKvinNo3PWAItyqtCJNcdNWDzbWRizMEPJK
N9AYlDckmF/X/1cZxWNyiltZZ542kezn
-----END PRIVATE KEY----- 
EOD;

  // Read arguments (encrypted session key and encrypted data)
  $EncodedKey  = $_GET['key'];
  $EncodedData = $_GET['data'];
  
  // Convert text to bytes
  $EncryptedKey  = base64_decode($EncodedKey);
  $EncryptedData = base64_decode($EncodedData);
  
  // Convert little-endian (Windows/EurekaLog/WinCrypt) to big-endian (PHP/OpenSSL)
  $EncryptedKey = strrev($EncryptedKey);
  
  // Decrypt the session key
  if (!openssl_private_decrypt($EncryptedKey, $Key, $PrivateKey)) {
    echo('{ "error": ' . json_encode('openssl_private_decrypt: ' . openssl_error_string()) . ' }');  
    die();  
  }

  // Decrypt the request
  $Data = pkcs5_unpad(mcrypt_decrypt(MCRYPT_TWOFISH, $Key, $EncryptedData, MCRYPT_MODE_ECB));

  // Convert JSON-text to JSON-object
  $Data = json_decode($Data, true);  
  
  // Simple checks to validate the request (just an example)
  $Request = $Data['request'];
  if (($Request['version'] < 1) || ($Request['version'] > 1)) { 
    echo('{ "error": "Unsupported request" }');
    die(); 
  }
  
  // Are we being asked for a license?
  if ($Request['type'] == 'license') {
	  
    // Validate the requester
    $User = $Data['user'];
    // Just an example
    $OK = (($User['login'] == 'input-from-edit1') && ($User['password'] == 'input-from-edit2'));
    if ($OK) {
		
      // Somehow obtain the license for the requester
      // Real app would probably send encrypted license
      $License = 'This is just an example license';
	  
      // Sign the license
      openssl_sign($License, $Signature, $PrivateKey, OPENSSL_ALGO_SHA1);
	  
      // Convert big-endian (PHP/OpenSSL) to little-endian (Windows/EurekaLog/WinCrypt)
      $Signature = strrev($Signature);
	  
      // Convert bytes to text
      $EncodedLicense   = base64_encode($License);
      $EncodedSignature = base64_encode($Signature);
	  
      // Return license and digital signature to a caller
      echo('{ "license": ' . json_encode($EncodedLicense) . ', "signature": ' . json_encode($EncodedSignature) . ' }');
	  
    } else {
      echo('{ "error": "Access Denied" }');
      die();
    }
  }
  
  echo('{ "error": "Unsupported request" }');