Public Key Encryption with PHP
I’m not an expert when it comes to PHP or cryptography. This post is written in an attempt to help me to put the pieces together.
I understand the principles behind public key cryptography. There are plenty of online resources that explain it but what they don’t explain is how to implement it in a real life situation. I know there are libraries for most programming languages but the gulf between the introductory tutorials and the libraries’ documentation is huge.
What is Public Key Cryptography
Public key cryptography, sometimes referred to as asymmetric cryptography, is a method of encryption and authentication which relies on two separate keys, one public and one private key. It is up to the receiver to facilitate the process by creating a key pair and making the public key known to the person or persons that wish to send me encrypted messages.
The sender encrypts the message using a known algorithm and the recipient’s public key. The message can then only be decrypted using the corresponding private key.
Implementing with PHP
From the above, we can see that, assuming the key pair is already generated, all we really want is two functions, one to encrypt and one to decrypt:
encrypt( $message, $public_key ); decrypt( $encrypted_msg, $private_key );
To a certain extent, that is what we have with the libraries, once installed with their code accessible.
Halite / Paragonie
Halite is a crypto library for PHP. It’s documentation says that it’s a user friendly wrapper for the libsodium PHP library. Libsodium being PHP’s module for dealing with all things cryptographic. The website says that it needs to be installed using PECL but, after a lot of messing around, I found that libsodium is now bundled with PHP by default so you just need to enable it.
sudo phpenmod sodium
What caused me so much confusion was the Halite instructions saying that I could verify libsodium was installed with a simple script.
var_dump( [ \Sodium\library_version_major(), \Sodium\library_version_minor() ] );
I kept getting that the functions didn’t exist so assumed that meant it wasn’t installed. I’ve since concluded that the functions must be depreciated (or maybe it’s the namespace?) because printing out some equivalent libsodium global variables did work.
var_dump([ SODIUM_LIBRARY_MAJOR_VERSION, SODIUM_LIBRARY_MINOR_VERSION, SODIUM_LIBRARY_VERSION ]);
You can also check if it’s enabled from the command line with:
php -i | grep "sodium"
You should get something like:
/etc/php/7.4/cli/conf.d/20-sodium.ini, sodium sodium support => enabled libsodium headers version => 1.0.18 libsodium library version => 1.0.18
Once you’ve confirmed you’ve got libsodium working you are ready to install Halite. They said to do this using composer and it worked perfectly as you would expect:
composer require paragonie/halite:4.1
Get the most up to date version number from their Github instructions, in my case it was 4.1.
From here, the instructions seemed to work pretty well. You obviously need to include the files in your application. Assuming you also used composer then you’re just including the autoload.php file as normal. I was using a Laravel application because I want to use this public-key cryptography in an application I’m building. In any case, to have access to the classes included in the library you just need to tell your script to use them:
use ParagonIE\Halite\KeyFactory; use ParagonIE\Halite\Symmetric\Crypto as Symmetric; use ParagonIE\Halite\Asymmetric\Crypto as Asymmetric; use ParagonIE\Halite\HiddenString;
The KeyFactory class is used for generating keypairs so you can do something like this:
$keypair = KeyFactory::generateEncryptionKeyPair();
The public and private keys will then be accessible via the following methods:
$privateKey = $keypair->getSecretKey(); $publicKey = $keypair->getPublicKey();
To encrypt a message, as discussed earlier, when using someone’s public key then you would use the Asymmetric Crypto class, which we’ve given an alias of Asymmetric:
$message = new HiddenString( “A really secret message” ); $encrypted = Asymmetric::seal( $message, $publicKey );
Two things worthy of note here. Firstly, we have put the message to be encrypted through an instance of the HiddenString class. This class is used to make sure that the message is not stored in memory and therefore is not accessible after the encryption is taken care of.
Secondly, the asymmetric method we are using is seal rather than encrypt. The encrypt method also expects the senders private key. I believe this is for two reasons, that it will be encrypted in such a way that the message can also be decrypted by the senders private key and that the encrypted message will be signed by the senders private key.
For my purposes and in an effort to keep things simple I went with the seal method. The Halite documentation calls this method anonymous. Presumably because the recipient does not need to know who created the encrypted message.
When it comes to decrypting the “sealed” message, the recipient needs to do something like the following:
$privKey = KeyFactory::loadEncryptionSecretKey($private); $decrypted = Asymmetric::unseal($message, $privKey); $message = $decrypted->getString();
Notice the use of the KeyFactory method loadEncryptionSecretKey() here. Before, we generated a keypair and then used the generated public key in the encryption. In reality, the person sending the message is not going to be the person who generates the keys. The recipient generates the keys and then gives the public key to the sender (or more generally makes it publicly known) so that they can use it for encrypting messages before sending. Therefore, we need to load the key, in this case, from a file.
You can have a look through the KeyFactory.php file and see what other methods are available. There is everything you would expect, for example:
loadEncryptionPublicKey() generateSignatureKeyPair() deriveEncryptionKey()
With everything so far, we should now be able to fully implement a public-key cryptography system into any PHP application. However, as always, there are still some questions outstanding.
1. What exactly is the algorithm used with Halite and how do you encrypt and decrypt messages that use a different algorithm?
2. Practical implementation of having separate keys for signing and encrypting.
Can you help me to answer the unanswered questions above? Let me know in the comments below. I’ve no doubt that, when it comes to programming, the cryptographers are some of the smartest people around but they are often not so great at explaining things.
I’m currently working on a crypto project that needs to implement public-key cryptography for message communication and authentication. If you’d like to be kept up to date then subscribe to my mailing list or check back soon.