Paragon Initiative Enterprises Blog

The latest information from the team that develops cryptographically secure PHP software.

Solve All Your Cryptography Problems in 3 Easy Steps

Last year, we began developing Halite, a FOSS high-level wrapper for the PHP bindings to libsodium. We use Halite extensively in our own projects (including our upcoming CMS which has quite a few of its own innovative cryptography features baked-in).

As of version 2.1.0, we are confident that Halite solves all of the application-layer cryptography problems that most PHP developers face; and it does so in three easy steps. (For transport-layer cryptography, you should still use TLS, of course.)

The Three Steps to Simply Secure Cryptography Development

Step One: Managing Cryptography Keys

Managing keys in Halite is drop-dead simple. You don't need to stress over the details. This is best demonstrated by example:

<?php
use \ParagonIE\Halite\KeyFactory;

// Generate a new random encryption key:
$encryptionKey = KeyFactory::generateEncryptionKey();

Once you have a cryptography key, you can save it to a file and then reload it like so:

// Saving a key to a file:
KeyFactory::save($encryptionKey, '/path/to/encryption.key');

// Loading the key from a file:
$key = KeyFactory::loadEncryptionKey('/path/to/encryption.key');

That's all there is to managing keys.

  1. Generate a new key, which will be secure, then save it somewhere.
  2. Load it every time you need to use it.

Alternatively, if you wish to derive the key from a password, you can just do this:

<?php
use \ParagonIE\Halite\KeyFactory;

// Do this ONCE per key:
$salt = random_bytes(16);

// Generate a new random encryption key:
$encryptionKey = KeyFactory::deriveEncryptionKey(
    $password,
    $salt                // Generated by random_bytes(16), never rand() or mt_rand()
    KeyFactory::MODERATE // (This third parameter is totally optional.)
                         // also allows KeyFactory::INTERACTIVE or KeyFactory::SENSITIVE
                         // defaults to KeyFactory::INTERACTIVE
);

There are six types of keys Halite uses, which correspond to distinct cryptography features:

  • Symmetric Cryptography (both parties know the same key)
    • AuthenticationKey - used for authenticating messages
    • EncryptionKey - used for encrypting messages
  • Asymmetric Cryptography (both parties have their own secret key, and exchange their public keys)
    • EncryptionPublicKey
    • EncryptionSecretKey
    • SignaturePublicKey
    • SignatureSecretKey

To make keys easier to organize, you can just generate a KeyPair object for asymmetric keys. This isn't necessary; if you have the *SecretKey you can produce the corresponding *PublicKey:

$signSecretKey = KeyFactory::generateSignatureSecretKey();
$signPublicKey = $signSecretKey->derivePublicKey();
// $signPublicKey is a SignaturePublicKey object

Step Two: Encrypting or Authenticating with Halite

Once you have your keys set up, using Halite is a walk in the park.

Symmetric-Key Encryption

This is all you have to do:

<?php
use \ParagonIE\Halite\{
    Symmetric\Crypto as Symmetric,
    KeyFactory
};

// Loading the key from a file:
$key = KeyFactory::loadEncryptionKey('/path/to/encryption.key');

// And now, to encrypt a message:
$encrypted = Symmetric::encrypt('this is your plaintext', $key);

You don't need to worry about accidentally passing a weak key to encrypt(); it requires an EncryptionKey object. You don't need to know what a nonce is; Halite generates it for you. You don't need to worry about chosen-ciphertext attacks; Halite strictly uses authenticated encryption behind-the-scenes.

See below for how to decrypt these messages.

Symmetric-Key Message Authentication

If you only need to authenticate a message (e.g. for API communication) and encryption isn't necessary, Halite has you covered too.

<?php
use \ParagonIE\Halite\{
    Symmetric\Crypto as Symmetric,
    KeyFactory
};

// Loading the key from a file:
$key = KeyFactory::loadAuthenticationKey('/path/to/authentication.key');
 
// And now, to authenticate a message:
$authCode = Symmetric::auth('this is your plaintext', $key);
See below for how to verify these messages.

Asymmetric-Key Encryption

There are two flavors of asymmetric (Public-Key) encryption offered by Halite:

  • Authenticated Public-Key Encryption, which uses your EncryptionSecretKey and their EncryptionPublicKey. Both the sender and receiver can decrypt these messages.
  • Anonymous Public-Key Encryption, which only uses your recipient's EncryptionPublicKey. Only the recipient can decrypt these messages.

The authenticated case uses encrypt() and decrypt(), like so:

<?php
use \ParagonIE\Halite\{
    Asymmetric\Crypto as Asymmetric,
    KeyFactory
};

$aliceSecretKey = KeyFactory::loadEncryptionSecretKey('/path/to/enc.secret.key');
$BobPublicKey = KeyFactory::loadEncryptionPublicKey('/path/to/bob.enc.public.key');

// And now, to encrypt a message:
$encrypted = Asymmetric::encrypt(
    'this is your plaintext',
    $aliceSecretKey,
    $bobPublicKey
);

The anonymous case uses seal() and unseal(), like so:

<?php
use \ParagonIE\Halite\{
    Asymmetric\Crypto as Asymmetric,
    KeyFactory
};

$bobPublicKey = KeyFactory::loadEncryptionPublicKey('/path/to/bob.enc.public.key');

// And now, to encrypt a message:
$encrypted = Asymmetric::seal(
    'this is your plaintext',
    $bobPublicKey
);
See below for how to decrypt these messages.

Asymmetric-Key Message Authentication (Digital Signatures)

This is as straightforward as you might expect:

<?php
use \ParagonIE\Halite\{
    Asymmetric\Crypto as Asymmetric,
    KeyFactory
};

// Loading the key from a file:
$secretKey = KeyFactory::loadSignatureSecretKey('/path/to/sign.secret.key');
 
// And now, to authenticate a message:
$signature = Asymmetric::sign('this is your message', $secretKey);
See below for how to verify digital signatures.

Step Three: Decrypt or Verify

Symmetric-Key Decryption

Following from the symmetric-key encryption example, decryption is straightforward:

$plaintext = Symmetric::decrypt($encrypted, $key);

If the contents of $encrypted were tampered with, instead of getting a result, it will throw an \ParagonIE\Halite\Alerts\InvalidMessage exception.

You may choose to catch the exception (i.e. to handle the failure gracefully for the sake of consistent user experience). If you do not catch it, it will terminate script execution rather than fail open. (If you somehow forget to turn display_errors off, your keys will not be exposed by a stack trace.)

This will never return FALSE; there is no need to check the return value.

Symmetric-Key Message Verification

Following from the symmetric-key message authentication example, verification is straightforward:

if (Symmetric::verify('this is your plaintext', $key, $authCode)) {
    // Success
} else {
    // Invalid authentication code for this message
}

Asymmetric-Key Decryption

To decrypt a message that was encrypted using encrypt():

<?php
use \ParagonIE\Halite\{
    Asymmetric\Crypto as Asymmetric,
    KeyFactory
};

$bobSecretKey = KeyFactory::loadEncryptionSecretKey('/path/to/enc.secret.key');
$alicePublicKey = KeyFactory::loadEncryptionPublicKey('/path/to/alice.enc.public.key');

// And now, to decrypt a message:
$plaintext = Asymmetric::decrypt(
    $encrypted,
    $bobSecretKey,
    $alicePublicKey
);

The unseal() API is as simple as seal():

<?php
use \ParagonIE\Halite\{
    Asymmetric\Crypto as Asymmetric,
    KeyFactory
};

$bobSecretKey = KeyFactory::loadEncryptionSecretKey('/path/to/enc.secret.key');

// And now, to encrypt a message:
$plaintext = Asymmetric::unseal(
    $encrypted,
    $bobSecretKey
);

As with symmetric-key decryption, if the encrypted message is tampered with in-transit, these methods will throw an InvalidMessage exception rather than return FALSE.

Asymmetric-Key Digital Signature Verification

Verifying digital signatures is as straightfoward as creating digital signatures:

<?php
use \ParagonIE\Halite\{
    Asymmetric\Crypto as Asymmetric,
    KeyFactory
};

// Loading the key from a file:
$publicKey = KeyFactory::loadSignaturePublicKey('/path/to/sign.public.key');

if (Asymmetric::verify($message, $publicKey, $signature)) {
    // Valid signature for this message
} else {
    // Incorrect signature
}

There is No Step Four

That's it. You've just solved 90% of the cryptography use-cases in three easy steps:

  1. Generate/derive keys (and store them for later use).
  2. Encrypt/authenticate your messages.
  3. Decrypt/verify your messages.

It could hardly be any simpler while still being secure.

What about the other 10% of use-cases?

Halite has most of them covered as well, and they also only require up to two steps to implement once your keys exist. Read on to learn more about any specific solution to a problem you encounter.

The Other Problems Solved By Halite

Encrypted Password Hashes

As mentioned in our previous blog post about the cryptography powering CMS Airship, Halite's Password class offers encrypted password hashes, which allows you to add a level of security above the already impressive threshold of Argon2i in hardware-separated environments. (An attacker cannot even begin to attempt to crack the Argon2i hashes, since they're encrypted, and the attacker cannot obtain the key from a database server compromise.)

<?php
use \ParagonIE\Halite\{
    KeyFactory,
    Password
};

$key = KeyFactory::loadEncryptionKey('/path/to/encryption.key');

$hash = Password::hash($password, $key);

As with key derivation, you may pass an optional KeyFactory::MODERATE or KeyFactory::SENSITIVE to the third parameter to increase the Argon2i cost factors. This probably isn't necessary (and may expose your servers to DoS attacks without proper rate-limiting).

Verifying passwords (and checking if a re-hash is needed):

if (Password::verify($password, $hash, $key)) {
    // Login successful, but first, check that our hash is still good (i.e. in case Halite updates):
    if (Password::needsRehash($hash, $key, KeyFactory::INTERACTIVE)) {
        $replaceStoredHash = Password::hash($password, $key);
    }
} else {
   // Incorrect username or password
}

File Cryptography

Halite has a File class to facilitate whole-file cryptography. File shines in situations where, for example, you need to encrypt/sign/seal a 10 GB file on a server with very little RAM (say, 128 MB).

Its public API is rather simple and mirrors the features outlined in the three steps outlined above.

  • File::checksum() - Calculates the Blake2b-512 checksum of a file.
  • File::encrypt() - Symmetric-key Encryption
  • File::decrypt() - Symmetric-key Decryption
  • File::seal() - Asymmetric-key Anonymous Encryption
  • File::unseal()- Asymmetric-key Anonymous Decryption
  • File::sign() - Calculate a digital signature of (the keyed checksum of) a file
    • BLAKE2b-512 with the SignaturePublicKey used as the BLAKE2b key
  • File::verify() - Verify the digital signature of (the keyed checksum of) a file

For example, encrypting a file with a public key (using the seal() API) looks like this:

<?php
use \ParagonIE\Halite\{
    File,
    KeyFactory
};

$bobPublicKey = KeyFactory::loadEncryptionPublicKey('/path/to/bob.enc.public.key');

// And now, to encrypt a message:
File::seal(
    '/path/to/input.file',
    '/path/to/output.file',
    $bobPublicKey
);

The recipient only needs to run:

File::unseal(
    '/path/to/encrypted.file',
    '/path/to/decrypted.file',
    $bobSecretKey
);

This feature is fully documented here.

BLAKE2 Merkle Trees

You can use Halite to build a BLAKE2 Merkle Tree, like so:

<?php
use \ParagonIE\Halite\Structure\{
    MerkleTree,
    Node
};

$tree = new MerkleTree(
    new Node('GENESIS BLOCK'),
    new Node('Next piece of data'),
    new Node('Yet another piece of data')
);
var_dump($tree->getRoot());
// string(64) "15343b62af2602fc9d0c93b440a4b0f6c745537a094e890c6bef08bebf71a72b"

A Merkle Tree is a building block for more powerful cryptography features (e.g. Keyggdrasil). It effectively provides an append-only data structure that can be independently audited (sans any practical hash collisions).

As of Halite 2.1.0, you can adjust the hash size of your Merkle tree as well as personalize all of the BLAKE2b hashes with a string unique to your application.

$tree->setPersonalizationString('some constant');
var_dump($tree->getRoot());
// string(64) "087d6d429ae665915bce2652c6036acc9d4584834179884887005aa84074744d"

$tree->setHashSize(16); // Raw bytes, not hexits
var_dump($tree->getRoot());
// string(32) "6a77dec5b6229be067aa7cf678ce32cf"

Merkle Trees are immutable. If you wish to append another node to the tree, use the getExpandedTree() method, which creates a new MerkleTree object.

$otherTree = $tree->getExpandedTree(
    new Node('Extra Data'),
    new Node('Something else goes here')
);
$otherTree->setHashSize(16); // Raw bytes, not hexits
var_dump($otherTree->getRoot());
// string(32) "ed3fbf1c01399fba7fe0c43086b95113"

Expanded trees will inherit the parent tree's personalization string and hash size parameters.

$newTree = new MerkleTree(
    new Node('GENESIS BLOCK'),
    new Node('Next piece of data'),
    new Node('Yet another piece of data'),
    new Node('Extra Data'),
    new Node('Something else goes here')
);
var_dump($newTree->getRoot());
// string(64) "9228af238afa1b94c30cc763a176dc3397f72fd6fca773146af40a03fc3f01f2"

Secure Encrypted Cookies

Many PHP developers over the years decided to offload session storage into the client's cookie. This has a lot of problems (they're strictly irrevocable if the client doesn't want to play nice, but mostly bad cryptography), which Halite's Cookie class intends to solve.

<?php
use \ParagonIE\Halite\{
    KeyFactory,
    Cookie
};

$key = KeyFactory::loadEncryptionKey('/path/to/encryption.key');

$cookieStorage = new Cookie($key);
// Store a value in an encrypted cookie:
    $cookieStorage->store('name', 'value');
// Retrieve a value from an encrypted cookie:
    $value = $cookieStorage->fetch('name');

That's Too Easy! What's the Catch?

Halite offers a lot of utility for PHP developers above and beyond what many PHP cryptography libraries try to offer. We're able to get away with covering this much ground, without introducing really dumb security vulnerabilities, because our underlying cryptography primitives are provided by libsodium.

We designed Halite to be as simple as possible so PHP developers who aren't also cryptography experts can successfully use it to its full potential. Even its internal code is, reportedly, easy for people who don't program in PHP to read.

It's time to ditch mcrypt, abolish hacky home-grown unauthenticated encryption protocols, and embrace the simple and the secure. Did I mention Halite was Free Software?

About the Author

P.I.E. Staff

Paragon Initiative Enterprises

Paragon Initiative Enterprises is a Florida-based company that provides software consulting, application development, code auditing, and security engineering services. We specialize in PHP Security and applied cryptography.


Need Technology Consultants?

Will tomorrow bring costly and embarrassing data breaches? Or will it bring growth, success, and peace of mind?

Our team of technology consultants have extensive knowledge and experience with application security and web/application development.

We specialize in cryptography and secure PHP development.

Let's Work Together Towards Success

Our Security Newsletters

Want the latest from Paragon Initiative Enterprises delivered straight to your inbox? We have two newsletters to choose from.

The first mails quarterly and often showcases our behind-the-scenes projects.

The other is unscheduled and gives you a direct feed into the findings of our open source security research initiatives.

Quarterly Newsletter   Security Announcements