The Sodium crypto library of PHP 7.2+

by Enrico Zimuel
Senior Software Engineer
Rogue Wave Software, Inc.


Sunshine PHP 2019, Miami (FL), February 8

About me

Cryptography

Cryptography is hard. Hard to design, hard to implement, hard to use, and hard to get right.

NaCl

  • NaCl: Networking and Cryptography library
  • High-speed software library for network communication, encryption, decryption, signatures, etc
  • by Prof. Daniel J. Bernstein, and others
  • Highly-secure primitives and constructions, implemented with extreme care to avoid side-channel attacks

Side-channel attack

Attack based on information gained from the implementation of a computer system, rather than weaknesses in the implemented algorithm itself

Decode RSA key using power analysis

Source: Protecting Against Side-Channel Attacks with an Ultra-Low Power Processor

Timing attack

An attacker measures the CPU time to perform some procedures involving a secret (e.g. encryption key). If this time depends on the secret, the attacker may be able to deduce information about the secret.

Example in PHP


function compare(string $expected, string $actual): bool
{
    $lenExpected = strlen($expected);
    $lenActual   = strlen($actual);
    if ($lenExpected !== $lenActual) {
        return false;
    }
    for($i=0; $i < $lenActual; $i++) {
        if ($expected[$i] !== $actual[$i]) {
            return false;
        }
    }
    return true;
}

What information an attacker can deduce?

Prevent timing attack

We need a constant-time string comparision function

  • PHP 5.6+: hash_equals(string $a, string $b)
  • Sodium: sodium_compare(string $a, string $b)
  • Pure PHP: Utils::compareStrings(string $a, string $b) in zend-crypt

Best timing attack

In 2006 Adi Shamir, Eran Tromer, and Dag Arne Osvik used a timing attack to discover, in 65 milliseconds, the secret key used in widely deployed software for hard-disk encryption

Source: Cache Attacks and Countermeasures: the Case of AES

Sodium crypto library

Sodium

  • Sodium (libsodium) is a fork of NaCl
  • A portable, cross-compilable, installable, packageable, API-compatible version of NaCl
  • Same implementations of crypto primitives as NaCl
  • Shared library and a standard set of headers (portable implementation)
  • Official web site: libsodium.org

Features

  • Authenticated public-key and authenticated shared-key encryption
  • Public-key and shared-key signatures
  • Hashing
  • Keyed hashes for short messages
  • Secure pseudo-random numbers generation

Algorithms in Sodium

Elliptic curves

Elliptic curves

Elliptic curves over ℤn

Add 2 points

A + B = C, A + C = D, A + D = E

Scalar multiplication

P + P = 2P

Given P and Q find k such that Q = kP is hard!

Sodium in PHP

  • Available (as standard library) from PHP 7.2
  • PECL extension (libsodium) for PHP 7.0/7.1
  • 85 functions with prefix sodium_
    e.g. sodium_crypto_box_keypair()

Example 1:

encrypt with a shared-key

Symmetric encryption


// code1.php at https://github.com/ezimuel/sodium-php-talk
$msg = 'This is a super secret message!';

// Generating an encryption key and a nonce
$key   = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); // 256 bit
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes

// Encrypt
$ciphertext = sodium_crypto_secretbox($msg, $nonce, $key);
// Decrypt
$plaintext = sodium_crypto_secretbox_open($ciphertext, $nonce, $key);

echo $plaintext === $msg ? 'Success' : 'Error';

Note: the encryption is always authenticated, you need to store also nonce + ciphertext

Algorithms: XSalsa20 to encrypt and Poly1305 for MAC

Example 2:

authenticate with a shared-key

Symmetric authentication


// code2.php at https://github.com/ezimuel/sodium-php-talk
$msg = 'This is the message to authenticate!';
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); // 256 bit

// Generate the Message Authentication Code
$mac = sodium_crypto_auth($msg, $key);

// Altering $mac or $msg, verification will fail
echo sodium_crypto_auth_verify($mac, $msg, $key) ? 'Success' : 'Error';

Note: the message is not encrypted

Algorithm: HMAC-SHA512

Example 3:

sending secret messages

Public-key encryption


// code3.php at https://github.com/ezimuel/sodium-php-talk
$aliceKeypair = sodium_crypto_box_keypair();
$alicePublicKey = sodium_crypto_box_publickey($aliceKeypair);
$aliceSecretKey = sodium_crypto_box_secretkey($aliceKeypair);

$bobKeypair = sodium_crypto_box_keypair();
$bobPublicKey = sodium_crypto_box_publickey($bobKeypair); // 32 bytes
$bobSecretKey = sodium_crypto_box_secretkey($bobKeypair); // 32 bytes

$msg = 'Hi Bob, this is Alice!';
$nonce = random_bytes(SODIUM_CRYPTO_BOX_NONCEBYTES); // 24 bytes

$keyEncrypt = $aliceSecretKey . $bobPublicKey;
$ciphertext = sodium_crypto_box($msg, $nonce, $keyEncrypt);

$keyDecrypt = $bobSecretKey . $alicePublicKey;
$plaintext = sodium_crypto_box_open($ciphertext, $nonce, $keyDecrypt);
echo $plaintext === $msg ? 'Success' : 'Error';

Note: it provides confidentiality, integrity and non-repudiation

Algorithms: XSalsa20 to encrypt, Poly1305 for MAC, and XS25519 for key exchange

Example 4:

Digital signature

Digital signature


// code4.php at https://github.com/ezimuel/sodium-php-talk
$keypair = sodium_crypto_sign_keypair();
$publicKey = sodium_crypto_sign_publickey($keypair); // 32 bytes
$secretKey = sodium_crypto_sign_secretkey($keypair); // 64 bytes

$msg = 'This message is from Alice';
// Sign a message
$signedMsg = sodium_crypto_sign($msg, $secretKey);
// Or generate only the signature (detached mode)
$signature = sodium_crypto_sign_detached($msg, $secretKey); // 64 bytes

// Verify the signed message
$original = sodium_crypto_sign_open($signedMsg, $publicKey);
echo $original === $msg ? 'Signed msg ok' : 'Error signed msg';
// Verify the signature
echo sodium_crypto_sign_verify_detached($signature, $msg, $publicKey) ?
     'Signature ok' : 'Error signature';

Note: the message is not encrypted, signedMsg includes signature + msg

Algorithm: Ed25519

Example 5:

AES-GCM

AEAD AES-256-GCM


// code5.php at https://github.com/ezimuel/sodium-php-talk
if (! sodium_crypto_aead_aes256gcm_is_available()) {
    throw new \Exception("AES-GCM is not supported on this platform");
}
$msg = 'Super secret message!';
$key = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES);
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES);

// AEAD encryption
$ad = 'Additional public data';
$ciphertext = sodium_crypto_aead_aes256gcm_encrypt(
    $msg,
    $ad,
    $nonce,
    $key
);
// AEAD decryption
$decrypted = sodium_crypto_aead_aes256gcm_decrypt(
    $ciphertext,
    $ad,
    $nonce,
    $key
);
if ($decrypted === false) {
    throw new \Exception("Decryption failed");
}
echo $decrypted === $msg ? 'OK' : 'Error';

Note: you need to store also ad and nonce + ciphertext

Example 6:

store passwords safely

Argon2i


// code6.php at https://github.com/ezimuel/sodium-php-talk
$password = 'password';

$hash = sodium_crypto_pwhash_str(
    $password,
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
); // 97 bytes

echo sodium_crypto_pwhash_str_verify($hash, $password) ?
     'OK' : 'Error';

An example of Argon2i hash:

$argon2id$v=19$m=65536,t=2,p=1$EF1BpShRmCYHN7ryxlhtBg$zLZO4IWjx3E...

Argon2 in PHP 7.2


// code7.php at https://github.com/ezimuel/sodium-php-talk
$password = 'password';

// Argon2i without Sodium
$hash = password_hash($password, PASSWORD_ARGON2I); // 95 bytes
// Argon2id with PHP 7.3+
$hash2 = password_hash($password, PASSWORD_ARGON2ID); // 96 bytes

echo password_verify($password, $hash) ? 'OK' : 'Error';
echo password_verify($password, $hash2) ? 'OK' : 'Error';

Comparing with Sodium:


$argon2id$v=19$m=65536,t=2,p=1$EF1BpShRmCYH... // 97 bytes, Sodium
$argon2id$v=19$m=1024,t=2,p=2$R3ZTLktFd1Shp..  // 96 bytes, PHP 7.3
$argon2i$v=19$m=1024,t=2,p=2$Y3pweEtMdS82SG... // 95 bytes, PHP 7.2

Note: password_hash() is not compatible with sodium_crypto_pwhash_str()

Example 7:

Derive a key from a user's password

Password are bad

  • Not random
  • Predictable (most of the time)
  • Only a subset of ASCII codes (typically 68 vs 256)
  • Never use it as encryption/authentication key!
  • Use KDF to derive a key from a password

Derive a key using Argon2i

Example: generating a binary key of 32 bytes


// code8.php at https://github.com/ezimuel/sodium-php-talk
$password = 'password';
$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);

$key = sodium_crypto_pwhash(
    32,
    $password,
    $salt,
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);

Note: you need to store also the salt to generate the same key from password

References

Thanks!

Source code in this presentation:
ezimuel/sodium-php-talk

Contact me: enrico [at] zimuel.it

Follow me: @ezimuel