Using Halite for Public Key Crypto with PHP

Categories: coding

This post is a follow on from Public Key Encryption with PHP which concluded with two unanswered questions:

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.

The algorithm used by Halite is either XSalsa20 (for symmetric encryption) or X25519 (for asymmetric). Authentication is symmetric only and uses BLAKE2b.

I’m primarily interested in asymmetric public key encryption because I want to be able to permit people to send and receive signed and encrypted messages from a web server to a mobile app that I’m designing. X25519 is good because it’s an elliptic curve algorithm meaning far more security with much smaller keys and therefore much faster.

However, further research tells me that just knowing the algorithm is not sufficient for making my app interoperable with other applications written in different programming languages. The whole library used is also important. A message encrypted with elliptic curve X25519 in, say, OpenSSL cannot necessarily be decrypted using Libsodium. I guess this is because of the intricacies they perform under the hood.

Halite is basically a user friendly wrapper for the PHP Libsodium library and, fortunately, Libsodium (or a suitable wrapper) is available for most other programming languages. It also seems to be the most straightforward so I’m going to stick with that.

This Stack Overflow answer suggests that we might be better switching to the underlying library instead of the Halite wrapper but we’ll get to that a bit later.

Before moving onto my experiments with signing and encryption keys, I’m just going to leave link here with a step by step guide to generating encryption keys and encrypting a message in Javascript and then decrypting it in PHP; all using the respective LibSodium libraries.

Practical implementation of having separate keys for signing and encrypting

The security reason for having separate signing and encryption keys is that you should always keep a backup of the encryption key but never of the signing key.

The reasoning being that if you lose your encryption key then you won’t be able to decrypt any messages previously received. Typically, you wouldn’t want to leave decrypted messages on your system. It’s much better to store them in their encrypted form and decrypt when required. Even if you did, imagine hardware failure, resulting in the need to change device and download the encrypted messages again, if you’ve lost the private key (perhaps stored on the same failed device) then that wouldn’t be possible. It a business setting, not being able to access previously signed documents could be a catastrophe, therefore, it’s important to securely back up your private key.

With signatures the opposite is true – you should never back up your private key. If you lose the private part of your signing key then previous recipients can still verify the authenticity of your sent messages using the public key. You should publicly revoke your lost key and generate another. Having a backup somewhere creates the risk that someone can access your backup and then dishonestly sign messages on your behalf.

Getting access to your backed up encryption key doesn’t have the same consequences. A hacker could use it to encrypt misleading information but they wouldn’t be able to convince anyone that it was you that created it.

Separate signing and encryption keys with Halite

Which brings me to the practical part of my Halite experiment. I started by creating two sets of keypairs for two people, Alice and Bob. That is an encryption keypair and a signing keypair for each.

$data[] = [
           'person' => 'alice',
           'seed' => 'These are really secret words',
           'salt' => '0123456789abcdef'
         ];

$data[] = [
           'person' => 'bob',
           'seed' => 'These are also really secret words but different',
           'salt' => '0123456789abcdef' //we can use the same salt
          ]; 

foreach($data as $data) {

    $folder = "keys/$data['person']/";
    $seed   = $data['seed'];
    $salt   = $data['salt'];

    $enc_pair = KeyFactory::deriveEncryptionKeyPair($seed, $salt);
    $sig_pair = KeyFactory::deriveSignatureKeyPair($seed, $salt);

    //save the encryption keys
    KeyFactory::save( $enc_pair->getPublicKey(), $folder . '/public/encryptKey.txt' );
    KeyFactory::save( $enc_pair->getSecretKey(), $folder . '/private/encryptKey.txt' );

    //save the signing keys
    KeyFactory::save( $enc_pair->getPublicKey(), $folder . '/public/signKey.txt' );
    KeyFactory::save( $enc_pair->getSecretKey(), $folder . '/private/signKey.txt' );
}

return 'Done';

I used the derive functions instead of just randomly generating the keys because that is part of my end plan. Besides, it’s good to experiment with the different options.

Now we have the keys saved we can encrypt and decrypt some messages. I’m going to use the encrypt rather than seal options this time because I also want the message to be authenticated regarding who sent it (and that’s why we generated signing keys).

Note: If you’re copying and pasting to quickly implement something then this didn’t work. This answer is at the bottom of this post.

/*
 *I'm Bob, going to send Alice a message
 */
function encrypt() {

    $keyfolder = 'keys/'; 
    $msg = new HiddenString("The most secret message in the world!!!"); 

    //We encrypt using the recipient's public encryption key
    $alice_public_encrypt = KeyFactory::loadEncryptionPublicKey( $keyfolder . 'alice/public/encryptKey.txt' ); 

    //Because Bob is signing, I used his signature key 
$bob_secret = KeyFactory::loadSignatureSecretKey( $keyfolder . 'bob/private/signKey.txt' ); 

    //This does the work
    $result = Asymmetric::encrypt( $msg, $bob_secret, $alice_public );

    //we use the getString() method to access the result
    return $result->getString();
}

I then copied the signed and encrypted message into a script ready to be decrypted and verified:

/*
* I'm Alice, just received an encrypted and signed message from Bob
*/
function decrypt($message) {

    //We need the private part of the encryption key
    $alice_private_encr = KeyFactory::loadEncryptionSecretKey( 'keys/alice/private/encryptKey.txt' );

    //The public part of the signature key
    $bob_pub_sign_key = KeyFactory::loadSignaturePublicKey( 'keys/bob/public/signKey.txt' );

    //Decrypt and verify
    $result = Asymmetric::decrypt( $message, $alice_private_encr, $bob_pub_sign_key );

return $result->getString();
}

All of the above resulted in an error saying that Asymmetric::decrypt expects an instance of KeyFactory::loadEncryptionPublicKey, which seemed strange. If you’re supposed to use a signature key for signing then why would it want an encryption key?

Also, if we really should be using the encryption key to authenticate with encrypt, why did Asymmetric::encrypt not through a similar error when we created the message.

I went back over the encrypt function and signed the message with the encryption key and then used Bob’s public encryption key while decrypting and all worked good.

It turns out that we didn’t need the separate keypairs after all. Halite actually insisted on us using the encryption keys for authentication. Further reading of the rather sporadic documentation does specifically say that you use the signature keys when only signing a message.

Honestly, I don’t know but I have everything working using just one set of keys and that’s fine by me. I’m going to post a question on Stack Overflow and try and find out if this is a bug or my original understanding was just wrong.

«

    Leave a Reply

    Your email address will not be published. Required fields are marked *