Using Themis in PHP

Introduction

The phpthemis extension provides PHP access to the features and functions of the Themis cryptographic library:

  • Key generation: the creation of public/private key pairs, used in Secure Message and Secure Session.
  • Secure Message: the secure exchange of messages between two parties. RSA + PSS + PKCS#7 or ECC + ECDSA (based on key choice), AES GCM container.
  • Secure Storage (aka Secure Cell): provides secure storage of record based data through symmetric encryption and data authentication. AES GCM / AES CTR containers.
  • Secure Session: the establishment of a session between two peers, thereby data can be more securely exchanged. EC + ECDH, AES container.

There are also example console utils available for the PHP wrapper for Themis (as well as for some other wrappers — see the full list here). They help understand the specific mechanics of encryption/decryption processes of this specific wrapper. You can find the example console utils for the PHP wrapper here.

Quickstart

Installing from Packages

If you prefer building from sources — dive here.

Requirements

PHP versions 5.6, 7.0, 7.1 or 7.2 installed in your system.

For Debian or Ubuntu

1. Import the public key used by Cossack Labs to sign packages:

wget -qO - https://pkgs.cossacklabs.com/gpg | sudo apt-key add -

Note: If you wish to validate key fingerprint, it is: 29CF C579 AD90 8838 3E37 A8FA CE53 BCCA C8FF FACB.

2. You may need to install the apt-transport-https package before proceeding:

sudo apt-get install apt-transport-https

3. Add Cossack Labs repository to your sources.list:

sudo add-apt-repository \
    "deb https://pkgs.cossacklabs.com/stable/$(lsb_release -is | awk '{print tolower($0)}') \
    $(lsb_release -cs) \
    main"

4. Update the apt package index:

sudo apt-get update

5. Choose and install Themis extension for your PHP version:

apt-cache search libphpthemis
sudo apt-get install libphpthemis-php<version>

Check for successful setup:

php --ri phpthemis

Building PHPThemis from source code

Requirements

In addition to the common set of build tools, Themis currently requires either the OpenSSL or LibreSSL package with the developer version of the package (as it provides header files). In either case, we strongly recommend you using the most recent version of these packages.

Installing Themis

Get Themis source code from GitHub

git clone https://github.com/cossacklabs/themis.git

Note that the default installation process assumes the use of the standard LibreSSL/OpenSSL library and will install Themis to the standard /usr/lib and /usr/include locations.

In a typical case, all you need to do is (depending on your rights, we assume unprivileged user shell with sudo available):

cd themis
sudo make install

If your path is different, please see Building and installing for more details on how to change it.

To build and run tests, you can use:

make test

All the tests need to be passed with no errors.

Building phpthemis

Once the Themis library is installed, do:

sudo make phpthemis_install

To run tests for php test suit, you can run:

make prepare_tests_all test_php

This will build and install phpthemis.so to the standard zend extension directory. To enable the extension, you should add this to your php.ini file:

extension=phpthemis.so

You can then verify if phpthemis is installed using:

php --ri phpthemis

Examples and Tests

Some code samples for Themis objects are available in docs/examples/php folder.

We've also prepared tests in tests/phpthemis. Run them using following line:

./tests/phpthemis/run_tests.sh

Using Themis

Private/public key pair generation

Themis supports both Elliptic Curve and RSA algorithms for asymmetric cryptography. Algorithm type is chosen according to the generated key type. Asymmetric keys are needed for Secure Message and Secure Session objects.

WARNING: When you distribute private keys to your users, make sure the keys are sufficiently protected. You can find the guidelines here.

NOTE: When using public keys of other peers, make sure they come from trusted sources.

Keypair generation interface

Elliptic Curve

To generate an Elliptic Curve key pair:

mixed phpthemis_gen_ec_key_pair( void )

Return Values

Returns an associative array containing a public and private 256 bit EC keys on success. NULL is returned on error. The keys contained in the array values are strings containing binary data.

Examples
$keys = phpthemis_gen_ec_key_pair();
if ($keys !== NULL) {
    $private_key = $keys['private_key'];
    $public_key  = $keys['public_key'];
}

RSA

To generate an RSA key pair:

mixed phpthemis_gen_rsa_key_pair( void )

Return Values

Returns an associative array containing a public and private key on success. NULL is returned on error. The keys contained in the array values are strings containing binary data.

Examples
$keys = phpthemis_gen_rsa_key_pair();
if ($keys !== NULL) {
    $private_key = $keys['private_key'];
    $public_key  = $keys['public_key'];
}

Secure Message

The Secure Message functions provide a sequence-independent, stateless, contextless messaging system. This may be preferred in cases that don't require frequent sequential message exchange and/or in low-bandwidth contexts. This is secure enough to exchange messages from time to time, but if you'd like to have Perfect Forward Secrecy and higher security guarantees, please consider using Secure Session instead.

The Secure Message functions offer two modes of operation:

In Sign/Verify mode, the message is signed using the sender's private key and is verified by the receiver using the sender's public key. The message is packed in a suitable container and ECDSA is used by default to sign the message (when RSA key is used, RSA+PSS+PKCS#7 digital signature is used).

In Encrypt/Decrypt mode, the message will be encrypted with a randomly generated key (in RSA) or a key derived by ECDH (in ECDSA), via symmetric algorithm with Secure Cell in seal mode (keys are 256 bits long).

The mode is selected by the sender supplying a valid public key of the receiver (encrypt/decrypt) or setting this parameter to NULL, or an empty string to use sign/verify.

Read more about Secure Message's cryptographic internals here.

Secure Message interface
mixed phpthemis_secure_message_wrap( string $senders_private_key, string $receivers_public_key, string $message )
mixed phpthemis_secure_message_unwrap( string $receivers_private_key, string $senders_public_key, string $secure_message )

Parameters:

  • phpthemis_secure_message_wrap is used for encryption/signing; returns encrypted or signed message, returns a string of binary data containing the encrypted or signed message on success. NULL is returned on error.
  • senders_private_key is the private (EC or RSA) key of the entity sending the message — it is assumed that the receiver has safely acquired the associated public key through other channels.
  • receivers_public_key is the public (EC or RSA) key of the entity receiving the message — it is assumed that the sender has safely acquired this key through other channels.
  • message plaintext message.

  • phpthemis_secure_message_unwrap is used for decryption/verifying; returns a string containing the original message on success. NULL is returned on error.

  • receivers_private_key is the private (EC or RSA) key of the entity receiving the message.
  • senders_public_key is the public (EC or RSA) key of the entity sending the message - it is assumed that the receiver has safely acquired this key through other means.
  • secure_message encrypted/signed message.
Examples

Encrypt/Decrypt:

$sender_keys   = phpthemis_gen_ec_key_pair();
$receiver_keys = phpthemis_gen_ec_key_pair();
$message = 'The best laid schemes of mice and men go oft awry';
$smessage = phpthemis_secure_message_wrap($sender_keys['private_key'],$receiver_keys['public_key'],$message);
$rmessage = phpthemis_secure_message_unwrap($receiver_keys['private_key'], $sender_keys['public_key'],$message_to_send);
echo "Received : $rmessage\n";

Sign/Verify:

$sender_keys   = phpthemis_gen_ec_key_pair();
$receiver_keys = phpthemis_gen_ec_key_pair();
$message = 'The best laid schemes of mice and men go oft awry';

// Passing NULL as receiver keys enables Sign/Verify mode
$smessage = phpthemis_secure_message_wrap($sender_keys['private_key'],NULL,$message);
$rmessage = phpthemis_secure_message_unwrap(NULL, $sender_keys['public_key'], $message_to_send);
echo "Received : $rmessage\n";

Secure Cell

The Secure Сell functions provide the means of protection for arbitrary data contained in stores, such as database records or filesystem files. These functions provide both strong symmetric encryption and data authentication mechanisms.

The general approach is that given:

  • input: some source data to protect;
  • key: a password;
  • context: plus an optional "context information";

Secure Cell functions will produce:

  • cell: the encrypted data;
  • authentication tag: some authentication data.

The purpose of the optional "context information" (i.e. a database row number or file name) is to establish a secure association between this context and the protected data. In short, even when the password is known, if the context is incorrect, then decryption will fail.

The purpose of the authentication data is to validate that given a correct password (and context), the decrypted data is indeed the same as the original source data.

The authentication data must be stored somewhere. The most convenient way is to simply be appended it to the encrypted data, but this is not always possible due to the storage architecture of your application. The Secure Cell functions offer variants that address this issue in different ways.

The encryption algorithm used by Secure Cell (by default) is AES-256. The generated authentication data is 16 bytes long.

Secure Cell is available in 3 modes:

  • Seal mode: the mode that is the most secure and easy to use. Your best choice most of the time.
  • Token protect mode: the mode that is the most secure and easy to use. Your best choice most of the time.
  • Context imprint mode: length-preserving version of Secure Cell with no additional data stored. Should be used carefully.

You can learn more about the underlying considerations, limitations, and features here.

Secure Cell Seal mode interface

Encryption:

mixed phpthemis_scell_seal_encrypt( string $password, string $source_data [, string $context] )

Parameters:

  • password the password to be used for encryption.
  • source_data the data to be encrypted.
  • context the optional context (see above).

Return Values

Returns a string of binary data containing the encrypted data with the authentication data appended on success. NULL is returned on error.

Decryption:

mixed phpthemis_scell_seal_decrypt( string $password, string $encrypted_data [, string $context] )

Parameters:

  • password the password to be used for decryption.
  • encrypted_data the data to be decrypted.
  • context the optional context (see above).

Return Values

On success returns a string of binary data containing the original data. NULL is returned on error.

Example
$password = 'not-so-secret';
$message  = 'The best laid schemes of mice and men go oft awry';
$context  = '12345';
$secure_cell = phpthemis_scell_seal_encrypt($password, $message, $context);
$decrypted   = phpthemis_scell_seal_decrypt($password, $secure_cell, $context);
echo "Decrypted: $decrypted\n";
Secure Cell Token protect Mode

For cases where it is not feasible to simply append authentication data to the encrypted data, the phpthemis_scell_token_protect_encrypt and phpthemis_scell_token_protect_decrypt functions allow these two elements to be handled separately.

Encryption:

mixed phpthemis_scell_token_protect_encrypt( string $password, string $source_data [, string $context] )

Parameters:

  • password the password to be used for encryption.
  • source_data the data to be encrypted.
  • context the optional context (see above).

Return Values

Returns an associative array, the elements of which are strings of binary data containing the encrypted data and the authentication data on success. NULL is returned on error.

Decryption:

mixed phpthemis_scell_token_protect_decrypt(string $password, string $encrypted_data, string $token [, string $context] )

Parameters:

  • password password to be used for decryption.
  • encrypted_data data to be decrypted.
  • token relevant authentication data (token).
  • context optional context (see above).

Return Values

Returns a string of binary data containing the original data on success. NULL is returned on error.

Example
$password = 'not-so-secret';
$message  = 'The best laid schemes of mice and men go oft awry';
$context  = '12345';
$secure_cell = phpthemis_scell_token_protect_encrypt($password, $message, $context);
$decrypted = phpthemis_scell_token_protect_decrypt($password, $secure_cell['encrypted_message'], $secure_cell['token'], $context);
echo "Decrypted: $decrypted\n";
Secure Cell Context Imprint Mode

When it's impossible to simply append authentication data to the encrypted data, and when there are no auxiliary storage media to retain authentication data, the phpthemis_scell_context_imprint_encrypt and phpthemis_scell_context_imprint_decrypt functions provide encryption with the user supplied context, but without the benefit of authentication. This means that the integrity of the data cannot be enforced and these functions should only be the preferred choice when the alternatives above are not viable.

Note: In Context Imprint mode the context is mandatory.

Encryption:

mixed phpthemis_scell_context_imprint_encrypt( string $password, string $source_data, string $context )

Parameters:

  • password the password to be used for encryption.
  • source_data the data to be encrypted.
  • context the mandatory context (see above).

Return Values

Returns a string of binary data containing the encrypted data without any authentication data on success. NULL is returned on error.

Decryption:

mixed phpthemis_scell_context_imprint_decrypt( string $password, string $encrypted_data , string $context )

Parameters:

  • password the password to be used for decryption.
  • encrypted_data the data to be decrypted.
  • context the mandatory context (see above).

Return Values

Returns a string of binary data containing the original data on success. NULL is returned on error.

Example
$password = 'not-so-secret';
$message  = 'The best laid schemes of mice and men go oft awry';
$context  = '12345';
$secure_cell = phpthemis_scell_context_imprint_encrypt($password, $message, $context);
$decrypted   = phpthemis_scell_context_imprint_decrypt($password, $secure_cell, $context);
echo "Decrypted: $decrypted\n";

Secure Session

Secure Session is a sequence- and session- dependent, stateful messaging system. It is suitable for protecting long-lived peer-to-peer message exchanges where the secure data exchange is bound to a specific session context.

Secure Session operates in two stages: session negotiation where the keys are established and cryptographic material is exchanged to generate ephemeral keys and data exchange the where exchanging of messages can be carried out between peers.

You can read a more detailed description of the process here.

As noted above, Secure Session is stateful. It is therefore implemented by phpthemis as an object rather than as static functions. It's important to note at this point that persisting a Secure Session object (for example across HTTP requests to PHP as either a CGI or an Apache Module) would:

a) present a range of unwanted security issues and

b) is simply not supported.

Thus PHP use of Secure Session should only be considered in the context of daemonised PHP processes and there are definitely alternatives to that you may wish to consider.

Note: The phpthemis implementation uses exceptions to handle error states.

Put simply, Secure Session takes the following form:

  • Both clients and server construct a Secure Session object, providing
    • an arbitrary identifier,
    • a private key, and
    • a callback function that enables it to acquire the public key of the peers with which they may establish communication.
  • A client will generate a "connect request" and by whatever means it will dispatch that to the server.
  • A server will enter a negotiation phase in response to a client's "connect request"
  • Clients and servers will exchange messages until a "connection" is established.
  • Once a connection is established, clients and servers may exchange secure messages according to whatever application level protocol was chosen.

In order to focus on the specific functionality of Secure session (rather than the communication required between the client and the server), the example code below simulates a client / server interaction.

The Callback for Peer Public Key Access
function get_pub_key_by_id($id) {
    global $key_store;
    $pubkey = '';
    if(!empty($key_store[$id])) {
        $pubkey = $key_store[$id]['public_key'];
    }
    return($pubkey);
}

The function name get_pub_key_by_id is required. The function should return a public key generated by phpthemis_gen_ec_key_pair or phpthemis_gen_rsa_key_pair.

Initialisation

The client and server keys are generated and stored in a global (accessible to the get_pub_key_by_id) function above. Both the client and the server session objects are constructed.

A Secure Session is established and messages are exchanged.

Note: The second and the third message do not require session negotiation.

global $key_store; // An arbitrary global

//Set up client and server keys
$key_store['server'] = phpthemis_gen_ec_key_pair();
$key_store['client'] = phpthemis_gen_ec_key_pair();

// Create Secure Session Objects
try
    {
    $server_session = new themis_secure_session('server', $key_store['server']['private_key']); 
    $client_session = new themis_secure_session('client', $key_store['client']['private_key']);
    }
catch (Exception $e)
    {
    echo "Session setup failed ...".$e->getMessage()."\n";
    exit;
    }

// Test messages:
$response=secure_session_client('This is a test message 1',$client_session,$server_session);
echo "Receiving ... (".$response['status'].") ".$response['message']."\n";
$response=secure_session_client('This is a test message 2',$client_session,$server_session);
echo "Receiving ... (".$response['status'].") ".$response['message']."\n";
$response=secure_session_client('This is a test message 3',$client_session,$server_session);
echo "Receiving ... (".$response['status'].") ".$response['message']."\n";
The client side

Initially, if the session is not established, the client generates a connect request. Subsequently, the client "negotiates" with the server by replying with its stateful interpretation of the server's response. Once "negotiation" is complete, the initial application level message is sent and the server's response is processed.

function secure_session_client($message_to_send,$client_session,$server_session) {
    $client_message = '';
    $server_response = array('status' => 0, 'message' => '');
    try {
        if (!$client_session->is_established()) {
            echo "Connecting ... \n";
            $client_message = $client_session->connect_request();
        }

        $ii = 1;
        while (!$client_session->is_established()) {
            echo "Negotiating ... ".$ii++."\n";
            $server_response=secure_session_server($client_message,$server_session);
            if ($server_response['status'] != 0) {return($server_response);}
            $client_message=$client_session->unwrap($server_response['message']);
        }

        //With the session established handle the actual message to send
        echo "Sending ... ".$message_to_send." \n";
        $client_message = $client_session->wrap($message_to_send);
        $server_response = secure_session_server($client_message,$server_session);
        if ($server_response['status'] != 0) {
            return($server_response);
        }
        $server_response['message'] = $client_session->unwrap($server_response['message']);
    }
    catch (Exception $e) {
        $server_response['status'] =-2; // A Client Error
        $server_response['message']=$e->getMessage();
    }
    return($server_response);
}
The server side

A minimal "application" level protocol returns an associative array containing "status" (0 for success, -1 on error) and "message" containing either the negotiation phase data, the application level response, or the exception message on error.

function secure_session_server($client_message,$server_session) {
    $server_response = array('status' => 0, 'message' => '');
    try {
        if (!$server_session->is_established()) {
            $server_response['message'] = $server_session->unwrap($client_message);
        }
        else {
            $client_message = $server_session->unwrap($client_message);
            $server_response['message'] = $server_session->wrap('Response to: '.$client_message);
        }
    }
    catch (Exception $e) {
        $server_response['status'] =-1; // A Server Error
        $server_response['message']=$e->getMessage();
    }

    return($server_response);
}

That's it! See the full example available in docs/examples/php.