What libsodium can do for you? An Introduction to Cryptography in PHP

(Read the 🇫🇷 version here)

Cryptography or cryptology is the practice and study of techniques for secure communication in the presence of third parties called adversaries. More generally, cryptography is about constructing and analyzing protocols that prevent third parties or the public from reading private messages[…]

via wikipedia

Have you ever needed to:

  • protect access to a resource without storing information in a database?
  • store sensitive data securely?
  • verify the authenticity of a message?

Thanks to this article, you will be able to implement these uses-cases in a safe way.

What is libsodium ?

Libsodium is a library written in C to do and to ease cryptography. It is modern (v1 in 2014), portable, written by Frank Denis 🇫🇷, and above all simple to use.

libsodium extension is available by default since php 7.2. For older versions, you could install the library as a PECL Extension. Instead of the PECL, it is also possible to use the polyfill sodium_compat.

libsodium extension will expose many PHP functions in order to simplify our cryptographic needs. You can check if the extension is installed with php --re sodium. This command will list all functions, classes, and constants defined by the extension.

Prerequisite to the crypto: Generate random numbers

Generate random number is something really hard in computer science. So much that companies like Cloudflare use LavaLamp to generate random numbers. Randomness is primary when doing crypto. A generator of (pseudo) random number is called Cryptographically Secure Pseudo-Random Number Generator (CSPRNG).

libsodium historically exposed many functions to generate such numbers. However two new functions, random_bytes() and random_int(), have been added to PHP 7.0 for this same purpose. This explains why these functions have not been ported during the integration of libsodium in PHP 7.2.

Some use cases?

We will cover many concrete use cases to demonstrate how we can use this extension.

How to protect access to a resource

Consider a website where the geographic position of a bus stop is displayed. The map provider (mapbox, google maps…) gives us an API, but this API is authenticated and rate limited (we can call this API only X times per day).

We need to protect our API endpoint that returns a map according to a few parameters. We might store all variants, but this design requires code, maintenance, and a lot of database space.

We will prefer an alternative that consists of signing requests.

  1. the server generates some HTML with an image tag:
    <img src="/image-map?lat=xxx&long=yyy&signature=zzzz">
  2. the browser requests the image;
  3. the server ensures the signature of xxx and yyy is zzzz;
  4. the server requests the image to the map provider;
  5. the server sends back the image to the browser.

It’s now impossible to ask a map that has not been generated by the server. It means no one can use your API credentials to generate its own maps.

To implement that, you need to generate a private key. You must store it and never lose it:

$key = sodium_crypto_generichash_keygen();

Then you need to generate a signature for a map:

// We build a "message" that contains all needed information
$msg = "lat=XXXX;long=YYYY";

// We generate the signature
$signature = sodium_crypto_generichash($msg, $key);
$signatureAsTxt = bin2hex($signature);
dump(['signature' => $signature, 'signatureAsTxt' => $signatureAsTxt]);

It will display something like:

array:2 [
  "signature" => b"\x19íOSÉý\x11YßÜ>B╩ûÐrõl\x02>^n b>ª©[ÖìÈ"
  "signatureAsTxt" => "19a14f5390ec1159e19a3e42ca96d172e46c023e5e6eb6623ea6b8b25b998dd4"
]

We can send this data to the client:

<img src="/image-map?lat=XXXX&long=YYYY&signature=19a14f5390ec1159e19a3e42ca96d172e46c023e5e6eb6623ea6b8b25b998dd4">

When an HTTP request comes in, we will do the same process, but we will verify if both signatures are the same:

$lat = $_GET['lat'];
$long = $_GET['long'];
$signatureAsTxt2 = $_GET['signature'];
$msg2 = "lat=$lat;long=$long";
$signature2 = sodium_crypto_generichash($msg2, $key);

// We check both signatures

$ok = hash_equals($signature2, hex2bin($signatureAsTxt2));
if (!$ok) {
    throw new \Exception('This request is not valid.');
}

Note: here we use hash_equals() instead of ===. It is more secure because it compares both strings in a time constant manner.

If the message generation ($msg) contains too many parameters, you can use sodium_crypto_generichash_*() functions:

$hashState = sodium_crypto_generichash_init();
sodium_crypto_generichash_update($hashState, 'XXXX');
sodium_crypto_generichash_update($hashState, 'YYYY');
$msg = sodium_crypto_generichash_final($hashState);
//...

How to store secret data in a file with a password

On some projects, developers can connect via SSH to production servers, but can’t add the SSH key to a server because the hosting platform does not support this feature, and you do not want to store all plain text password on your network (or on Google Drive, Dropbox, Github…) either (and you are right). However, storing an encrypted by password file in the git repository is fine to you? This section is made for you.

To implement that, you need to generate a private key. You must store it and never lose it:

$key = sodium_crypto_secretbox_keygen();

Then, you need to generate a nonce. Unlike the key, the nonce does not need to be private. We can store it next to the encrypted message.

$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);

Then, we can encrypt the message:

$encrypted = sodium_crypto_secretbox('my super secret data', $nonce, $key);

And to decrypt the encrypted message:

$decrypted = sodium_crypto_secretbox_open($encrypted, $nonce, $key);

How to authenticate a document

For this case, we can use the example of a server that generates invoices in PDF. It is quite easy to edit a PDF, so you want to guarantee the integrity of the document and its content. It means that you want to be sure that an attacker can not forge fake invoices. You may also want that anyone can validate if an invoice has really been emitted by your application. For example, an insurance company would verify that its client has really bought something on your website.

Thanks to this system, insurance companies do not need to communicate with you to authenticate an invoice.

To implement that, you need to generate a private key. You must store it and never lose it:

$keyPair = sodium_crypto_sign_keypair();

From this key, we will extract the private part. We will use this part to sign all documents. We will also extract the public part. Thiscan be published to anyone:

$keyPairSecret = sodium_crypto_sign_secretkey($keyPair);
$keyPairPublic = sodium_crypto_sign_publickey($keyPair);

Thanks to this key, we can sign the document:

$message = 'Hello';

$signature = sodium_crypto_sign_detached($message, $keyPairSecret);

Now, we can send the document with the signature to the client. Anyone will be able to authenticate it:

$ok = sodium_crypto_sign_verify_detached($signature, $message, $keyPairPublic);
if (!$ok) {
    throw new \Exception('The document has been modified.');
}

How to send a super secure message to a user?

In some cases, we want a level of security where even HTTPS is not enough. HTTPS can be compromised at several levels: a breach in SSL, a company doing a Man In The Middle, etc. To fix this issue, it is possible to generate a message that only the recipient will be able to read. To do so, we will use an asymmetric encryption (like in the previous chapter) but with more keys.

An email server/client is a very good example, since the security should be maximal.

The workflow is the following:

  1. Alice creates a pair of asymmetric keys (public and private);
  2. Bob creates a pair of asymmetric keys (public and private);
  3. They exchange the public parts;
  4. Alice encrypts her message with her own private key, then with Bob’s public key;
  5. Alice sends the encrypted message to Bob;
  6. Bob gets the message from Alice;
  7. Bob encrypts the message with his own private key, then with Alice’s public key;

Thanks to this double encryption:

  • The message is decryptable only by Bob;
  • Even Alice can not decrypt it even though she encrypted it;
  • Bob can verify that the message has been sent by Alice and not someone else;

To do that, you will need to do the following:

On Alice’s computer (usually a server)

// Keep this part secret!
$aliceKey = sodium_crypto_box_keypair();
$aliceKeySecret = sodium_crypto_box_secretkey($aliceKey);
// This key is transmitted to Bob
$aliceKeyPublic = sodium_crypto_box_publickey($aliceKey);

On Bob’s computer (usually in JavaScript in the browser, or on another server if it’s an API):

// Keep this part secret!
$bobKey = sodium_crypto_box_keypair();
$bobKeySecret = sodium_crypto_box_secretkey($bobKey);
// This key is transmitted to Alice
$bobKeyPublic = sodium_crypto_box_publickey($bobKey);

Alice will encrypt the message, then send it to Bob:

$message = 'hello';

$nonce = random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES);

$encryptionKey = sodium_crypto_box_keypair_from_secretkey_and_publickey($aliceKeySecret, $bobKeyPublic);
$encrypted = sodium_crypto_box($message, $nonce, $encryptionKey);

Then Bob will decrypt the message from Alice:

$decryptionKey = sodium_crypto_box_keypair_from_secretkey_and_publickey($bobKeySecret, $aliceKeyPublic);
$decrypted = sodium_crypto_box_open($encrypted, $nonce, $decryptionKey);

Utils

libsodium also exposes some utilities that are more secure than native PHP functions. Actually, libsodium is more secure regarding Side Channel Attacks

  • sodium_bin2hex() / sodium_hex2bin() : Conversion binary <-> hexa;
  • sodium_bin2base64() / sodium_base642bin() : Conversion binary <-> base64;
  • sodium_increment() : Increment a number (very big);
  • sodium_compare() / sodium_memcmp() : Compares two values in a constant time. However, since PHP 5.6, hash_equals() is enough;
  • sodium_pad() / sodium_unpad() : Pads a string with 0 to get a fixed size. This block an attacker from guessing the message size;
  • sodium_memzero() : Allows deleting a variable from the PHP memory.

But don’t worry. Native PHP functions are good enough. PHP, by its nature (language for making the web), is not very sensitive to SCA.

Conclusion

libsodium extension is native since PHP 7.2. You must know it if you want to increase the security of your application. Its API is simple and does not require strong skills in cryptography. However, it is badly documented on php.net (That’s why I wrote this article). Don’t hesitate to have a look at the documentation of the underlying C lib. It’s complete and full of examples.

Finally, Paragonie, a company specialized in computer security has published halite. It’s a high-level cryptography interface powered by libsodium. Its goal is to simplify the libsodium usage even more, to gather all best practices, and to add even more security.

Nos formations sur le sujet

  • Logo Symfony

    Symfony

    Formez-vous à Symfony, l’un des frameworks web PHP les plus connus au monde

blog comments powered by Disqus