If you're not familiar with cryptography terminology, read this page first. It covers everything from terms (e.g. nonce) to concepts (e.g. public-key encryption).
Last Friday at Day Camp 4 Developers, I presented a talk titled Cooking with Sodium in PHP 7.2, which was largely live-demoing the various cryptography features provided by libsodium. One of the questions I was asked by attendees was about knowing which feature to use to solve specific problems. This is the sort of problem that I suspect many people run into, so here's a quick reference table followed by a detailed explanation.
In the table below, all encryption modes utilize authenticated encryption.
Libsodium Function | Type | Notes / Use Case |
---|---|---|
Libsodium Function | Type | Notes / Use Case |
crypto_pwhash | Password hashing | Secure password storage, key derivation from user input |
crypto_generichash | Cryptographic hash function | Collision- and preimage-resistant; replace MD5/SHA1/etc. |
crypto_shorthash | Short-input hash function | Hash tables, bloom filters, cache lookup keys |
crypto_auth | Symmetric authentication | Both parties can verify and/or forge messages |
crypto_sign | Asymmetric authentication | Digital signature (anyone can verify, only sender can sign) |
crypto_aead_* | Symmetric encryption | Shared-secret encryption with additional data |
crypto_secretbox | Symmetric encryption | Compatibility with NaCl, TweetNaCl, etc. |
crypto_box | Asymmetric encryption | Both sender and receiver can decrypt |
crypto_box_seal | Asymmetric encryption | Only receiver can decrypt; sender is anonymous |
Solving Common Tasks with Libsodium
I need to store an encrypted or hashed password (actually never encrypted, only hashed)
Use crypto_pwhash_str()
and crypto_pwhash_str_verify()
.
I need to encrypt a string and then decrypt it later (on the same machine)
For simplicity: use crypto_secretbox()
and crypto_secretbox_open()
.
It's actually better to use crypto_aead_*
(especially if you use the sample code, but the secretbox
API is simple and secure.
I need to encrypt a string and then decrypt it later (on a different machine)
It depends!
Do you have a two-way data flow (A <=> B
) or a one-way data flow (A -> B
)?
If you're only ever encrypting on Node A, and only ever decrypting on Node B, you'll want crypto_box_seal()
and crypto_box_seal_open()
. This prevents the sender (Node A) from decrypting messages after they've been sent to recipient (Node B).
Otherwise, you'll simply want to use crypto_box()
and crypto_box_open()
.
I previously used mcrypt_encrypt() or openssl_encrypt() and need to migrate my data
First, decrypt your data as usual, then re-encrypt using crypto_secretbox()
and crypto_secretbox_open()
.
I previously used openssl_public_encrypt() / openssl_private_decrypt() and need to migrate my data
First, decrypt your data as usual, then re-encrypt using crypto_box
or crypto_box_seal
(see previous answer about encryption between different machines for guidance on which one you want).
Libsodium Function Analysis
PHP Developers: In PHP before 7.2 with libsodium from PECL, the functions below were defined in the Sodium
name space. In PHP 7.2, the namespaces were dropped in favor of a sodium_
prefix (to conform to the PHP internal development standards).
For example, in PHP 7.1 and below with ext/sodium from PECL, an example snippet for crypto_secretbox()
might look like this:
<?php
// PHP < 7.2 with `pecl install libsodium`
$key = str_repeat("\x80", 32);
$nonce = random_bytes(24);
$data = $nonce . \Sodium\crypto_secretbox("Test", $nonce, $key);
When PHP 7.2 is released, the interface will look like this instead:
<?php
// PHP >= 7.2
$key = str_repeat("\x80", 32);
$nonce = random_bytes(24);
$data = $nonce . sodium_crypto_secretbox("Test", $nonce, $key);
For sodium_compat users, your code will look like this:
<?php
// PHP >= 7.2
$key = str_repeat("\x80", 32);
$nonce = random_bytes(24);
$data = $nonce . ParagonIE_Sodium_Compat::crypto_secretbox("Test", $nonce, $key);
In the examples below, when we define a libsodium function, the actual function name will be either namespaced, prefixed, or a static method on ParagonIE_Sodium_Compat
. For simplicity, we're using the bare libsodium name since that part remains constant.
Hash Functions
crypto_pwhash()
Choose your own adventure:
- Key derivation:
crypto_pwhash()
- Password hashing/verification:
crypto_pwhash_str()
andcrypto_pwhash_str_verify()
Libsodium uses Argon2, the Password Hashing Competition winner. More specifically, it uses Argon2i (the side-channel resistant variant) in all current versions, but a future version may switch the default to Argon2id, which is more resistant to GPU attacks.
You want to use one of the crypto_pwhash
functions whenever you are taking a user-provided input (i.e a password) and your goal is to either:
- turn their password into a cryptography key, or
- store it for secure verification at a later date
Make sure you check the official libsodium documentation covering crypto_pwhash
for details on how to use it.
crypto_pwhash() Sample Code (PHP 7.2+)
<?php
/* This uses Argon2i with two numeric constants that correspond to
* the number of passes (OPSLIMIT) and the amount of memory to use
* (MEMLIMIT). A higher OPSLIMIT helps against CPU attacks, while a
* higher MEMLIMIT helps against GPU attacks.
*/
$storeInDatabase = sodium_crypto_pwhash_str(
$password,
SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);
/* Once that's stored, you can just test against the hash like so: */
if (sodium_crypto_pwhash_str_verify($storeInDatabase, $password)) {
/* Logged in! */
} else {
/* Incorrect password. */
}
crypto_generichash()
For PHP developers: This is sort of like hash()
, except it can also be hash_hmac()
if you pass a key, and it only allows BLAKE2b (so you don't have to select the algorithm).
You want to use crypto_generichash()
anywhere you'd normally use hash()
, md5()
, or sha1()
(unless "normally" implies something that would cause a cryptographer would look at you funny). crypto_generichash()
is attractive because it is all of the following:
- Collision-resistant
- Preimage-resistant
- Immune to length-extension attacks
- More secure than SHA256
- Faster than MD5
See also: the libsodium documentation on crypto_generichash
.
crypto_generichash() Sample Code (PHP 7.2+)
<?php
$someData = 'This is a test message';
$someSecretKey = random_bytes(32);
$hash = sodium_crypto_generichash($someData);
var_dump(mb_strlen($hash, '8bit')); /* int(32) */
$blake2mac = sodium_crypto_generichash($someData, $someSecretKey);
var_dump(mb_strlen($blake2mac, '8bit')); /* int(32) */
$truncated = sodium_crypto_generichash($someData, '', 16);
var_dump(mb_strlen($truncated, '8bit')); /* int(16) */
crypto_shorthash()
SipHash, the algorithm powering crypto_shorthash()
, is a secure keyed pseudo-random function. Or, in other words, it's a hash function like BLAKE2b, except it's only meant to be used with a key and its output size of 64 bits is too small to be collision-resistant or immune to brute-force searches.
Don't use crypto_shorthash()
in places where crypto_generichash()
would be appropriate. SipHash is better suited for building hash tables with a per-request key to resist hash-collision denial-of-service attacks. You can also use it for Bloom filters and cache lookups where micro-benchmarks matter. But generally, crypto_generichash()
and crypto_pwhash()
are probably better tools for the job.
See also: the libsodium documentation on crypto_shorthash
.
crypto_shorthash() Sample Code (PHP 7.2+)
<?php
$key = random_bytes(16);
$input = ['apple', 'boy', 'cat', 'dog', 'echo'];
$mapped = [];
foreach ($input as $item) {
$hash = sodium_crypto_shorthash($item, $key);
$mapped[bin2hex($hash)] = $item;
}
var_dump($mapped);
Authentication
crypto_auth()
crypto_auth()
is from NaCl, and serves to provide symmetric-key authentication. If you know about HMAC, you know what this does. PHP developers will only really only need crypto_auth()
and its counterpart crypto_auth_verify()
if you're aiming to interoperate with other NaCl/libsodium projects. Otherwise, hash_hmac()
and hash_equals()
accomplish the same thing.
crypto_auth()
is, in fact, HMAC-SHA512 truncated to 32 bytes.
It is important to note that, with HMAC, any party capable of verifying messages is also capable of signing them. That's what symmetric-key authentication means. If you need one entity to be able to sign, but everyone else in the universe to be able to only verify, then you want crypto_sign()
instead.
See also: the libsodium documentation on crypto_auth
.
crypto_auth() Sample Code (PHP 7.2+)
<?php
$key = random_bytes(32);
$message = 'authenticate me';
/* Get the message authentication code (MAC): */
$mac = sodium_crypto_auth($message, $key);
/* Verify a message: */
if (sodium_crypto_auth_verify($mac, $message, $key)) {
/* Verified */
} else {
/* Message has been tampered with; discard */
}
crypto_sign()
The crypto_sign
functions come in two flavors:
-
crypto_sign()
andcrypto_sign_open()
, which are useful for sending signed messages in one go. -
crypto_sign_detached()
andcrypto_sign_verify_detached()
, which are more generally useful.
Generally, you'll find yourself reaching for the latter two more in PHP land, but the original two are useful too.
Unlike crypto_auth
, crypto_sign
utilizes asymmetric (a.k.a. public-key) cryptography. You sign a message with your secret key, and anyone with your public key can verify the message.
Once again, the libsodium documentation for crypto_sign
is worth a read.
crypto_sign() Sample Code (PHP 7.2+)
<?php
$mySigningKeypair = sodium_crypto_sign_keypair();
$secretKey = sodium_crypto_sign_secretkey($mySigningKeypair);
$publicKey = sodium_crypto_sign_publickey($mySigningKeypair);
$message = 'authenticate me';
/* Sign the message, using your secret key (which is NOT given out): */
$signature = sodium_crypto_sign_detached($message, $secretKey);
/* Now validate the signature with your public key (which IS given out): */
if (sodium_crypto_sign_verify_detached($signature, $message, $publicKey)) {
/* Message was signed by me. */
} else {
throw new Exception('Invalid signature. Do not trust the message.');
}
Encryption
crypto_aead()
AEAD is a cryptographer acronym that stands for Authenticated Encryption with Associated Data. The general idea is that you have:
- Your plaintext, which gets encrypted into the ciphertext.
- Some other data, which doesn't touch the ciphertext.
- An authentication tag, which covers both the ciphertext and the other data.
You want to use crypto_aead
when you have either a pre-shared key or a negotiated one (e.g. via Elliptic Curve Diffie-Hellman).
AEAD modes are the preferred way to encrypt in 2017. Libsodium offers several options for its AEAD interface, all of which implement symmetric encryption. You will probably want to use, in order of preference:
- (One day):
crypto_aead_encrypt()
andcrypto_aead_decrypt()
- Not available yet.
- Will be available after the selection of CAESAR finalists.
-
crypto_aead_xchacha20poly1305_ietf_*()
- Allows you to use very large nonces, which can safely be generated randomly with no practical risk of accidental nonce reuse.
-
crypto_aead_chacha20poly1305_ietf_*()
- The standardized ChaCha20-Poly1305 variant.
-
crypto_aead_aes256gcm_*()
- Required specialized hardware support. Only use this if every device is guaranteed to have modern processors.
-
crypto_aead_chacha20poly1305_*()
- Implemented before the IETF standardized their nonce/counter sizes for use in TLS. Less widely supported.
The AEAD section of the libsodium documentation has more details about key/nonce sizes, etc.
crypto_aead_*() Sample Code (PHP with sodium_compat)
<?php
/**
* Wrap crypto_aead_*_encrypt() in a drop-dead-simple encryption interface
*
* @link https://paragonie.com/b/kIqqEWlp3VUOpRD7
* @param string $message
* @param string $key
* @return string
*/
function simpleEncrypt($message, $key)
{
$nonce = random_bytes(24); // NONCE = Number to be used ONCE, for each message
$encrypted = ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_encrypt(
$message,
$nonce,
$nonce,
$key
);
return $nonce . $encrypted;
}
/**
* Wrap crypto_aead_*_decrypt() in a drop-dead-simple decryption interface
*
* @link https://paragonie.com/b/kIqqEWlp3VUOpRD7
* @param string $message - Encrypted message
* @param string $key - Encryption key
* @return string
* @throws Exception
*/
function simpleDecrypt($message, $key)
{
$nonce = mb_substr($message, 0, 24, '8bit');
$ciphertext = mb_substr($message, 24, null, '8bit');
$plaintext = ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_decrypt(
$ciphertext,
$nonce,
$nonce,
$key
);
if (!is_string($plaintext)) {
throw new Exception('Invalid message');
}
return $plaintext;
}
$secretKey = random_bytes(32);
$message = 'Test message';
/* Encrypt the message: */
$ciphertext = simpleEncrypt($message, $secretKey);
/* Decrypt the message: */
try {
$decrypted = simpleDecrypt($ciphertext, $secretKey);
var_dump(hash_equals($decrypted, $message));
/* bool(true) */
} catch (Exception $ex) {
/* Someone is up to no good */
exit(255);
}
crypto_secretbox()
crypto_secretbox()
is from NaCl and implements symmetric (shared-key) authenticated encryption. secretbox
accepts a large nonce, which is safe to generate randomly (random_bytes()
in PHP, randombytes_buf()
in libsodium) and virtually never worry about reusing a nonce.
You want to use crypto_secretbox()
when you have either a pre-shared key or a negotiated one (e.g. via Elliptic Curve Diffie-Hellman).
If you have to choose between secretbox
and aead_xchacha20poly1305
, go for the AEAD mode. Otherwise, use secretbox
.
The relevant section of the libsodium documentation for crypto_secretbox
can be found here.
crypto_secretbox_*() Sample Code (PHP 7.2+)
<?php
$key = random_bytes(32);
$message = 'Yellow submarine';
/* For each encryption: */
$nonce = random_bytes(24); /* Never repeat this! */
$ciphertext = sodium_crypto_secretbox($message, $nonce, $key);
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
crypto_box()
crypto_box
is from NaCl and implements authenticated asymmetric (a.k.a. public-key) encryption. With crypto_box()
, both the sender and recipient can read messages and verify the other party sent them. However, it allows each party to negotiate their own private keys and share distinct secrets with each other, so talking with one person doesn't allow you to eavesdrop on another's conversations with them.
You want to use crypto_box()
where you'd normally implement RSA encryption. It's more commonly found in interactive protocols (e.g. messaging applications).
To learn more, see the libsodium documentation for crypto_box
.
crypto_box() Sample Code (PHP 7.2+)
<?php
# Setup:
/* On Node A */
$aliceKeypair = sodium_crypto_box_keypair();
$aliceSecretKey = sodium_crypto_box_secretkey($aliceKeypair);
$alicePublicKey = sodium_crypto_box_publickey($aliceKeypair);
// Then share $alicePublicKey with Node B
/* On Node B: */
$bobKeypair = sodium_crypto_box_keypair();
$bobSecretKey = sodium_crypto_box_secretkey($bobKeypair);
$bobPublicKey = sodium_crypto_box_publickey($bobKeypair);
// Then share $bobPublicKey with Node A
# Transmission:
/* Sending from Node A to Node B */
$message 'Hi there! :)';
$aliceToBob = $aliceSecretKey . $bobPublicKey;
$nonce = random_bytes(24); /* Never repeat this! */
$ciphertext = $nonce . sodium_crypto_box($message, $nonce, $aliceToBob);
/* On Node B, receiving an encrypted message from Node A */
$bobToAlice = $bobSecretKey . $alicePublicKey;
$nonce = mb_substr($ciphertext, 0, 24, '8bit');
$encrypted = mb_substr($ciphertext, 24, null, '8bit');
$decrypted = sodium_crypto_box_open($encrypted, $nonce, $bobToAlice);
# Alternatively:
/* Sending from Node B to Node A */
$message 'Hello yourself.';
$bobToAlice = $bobSecretKey . $alicePublicKey;
$nonce = random_bytes(24); /* Never repeat this! */
$ciphertext = $nonce . sodium_crypto_box($message, $nonce, $bobToAlice);
/* On Node A, receiving an encrypted message from Node B */
$aliceToBob = $aliceSecretKey . $bobPublicKey;
$nonce = mb_substr($ciphertext, 0, 24, '8bit');
$encrypted = mb_substr($ciphertext, 24, null, '8bit');
$decrypted = sodium_crypto_box_open($encrypted, $nonce, $aliceToBob);
crypto_box_seal()
crypto_box_seal()
is a libsodium exclusive, which allows the sender to encrypt a message that only the recipient can decrypt. Sealing APIs are sometimes referred to as anonymous public-key encryption, because although it provides ciphertext integrity, the sender is not authenticated like with crypto_box
.
You want to use crypto_box_seal
whenever you're encrypting information that you don't want the sender to be able to decrypt. For example, storing encrypted data in an online database that can only be decrypted with key that is kept offline in an air-gapped machine.
If you're trying to decide between crypto_box()
versus crypto_box_seal()
, ask yourself if the system that performed the encryption should be capable of decrypting the messages they sent. If the answer is no, you want crypto_box_seal()
. If you still need the sender to be authenticated, use crypto_sign()
before crypto_box_seal()
on the sending side and crypto_sign_open()
after crypto_box_seal_open()
on the receiving side.
As always, the documentation for crypto_box_seal
explains its usage in-depth.
crypto_box_seal() Sample Code (PHP 7.2+)
<?php
# Setup:
/* On Node B: */
$bobKeypair = sodium_crypto_box_keypair();
$bobPublicKey = sodium_crypto_box_publickey($bobKeypair);
// Then share $bobPublicKey with Node A
# Transmission:
/* Sending from Node A to Node B */
$message 'Hi there! :)';
$ciphertext = sodium_crypto_box_seal($message, $bobPublicKey);
/* On Node B, receiving an encrypted message from Node A */
$decrypted = sodium_crypto_box_seal_open($ciphertext, $bobKeypair);
Other Functions
If you're confused about any of the libsodium functions not listed here, refer to the official libsodium documentation.