The Sodium crypto library of PHP 7.2

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


phpDay 2018, Verona (Italy), May 12

About me

Overview

  • NaCl/Sodium libraries
  • Elliptic Curve Cryptography
  • Sodium in PHP 7.2:
    1. Encrypt with a shared-key
    2. Authenticate with a shared-key
    3. Sending secret messages
    4. Digital signature
    5. AEAD AES-GCM
    6. Store passwords safely
    7. Derive a key from a user's password

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;
}

Prevent timing attack *


function compare(string $expected, string $actual): bool
{
    $lenExpected = strlen($expected);
    $lenActual   = strlen($actual);
    $len         = min($lenExpected, $lenActual);

    $result = 0;
    for ($i = 0; $i < $len; $i++) {
        $result |= ord($expected[$i]) ^ ord($actual[$i]);
    }
    $result |= $lenExpected ^ $lenActual;
    return ($result === 0);
}

* constant-time comparison

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

$ y^2 = x^3 + ax + b $

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


$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


$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


$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


$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


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


$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


$password = 'password';

// Argon2i without Sodium
$hash = password_hash($password, PASSWORD_ARGON2I); // 95 bytes

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

Comparing with Sodium:


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

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


$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

Utility in Sodium

  • Wiping Sensitive Data from Memory:
    sodium_memzero(&$secret)
  • Hex2bin / Bin2Hex:
    
    sodium_hex2bin(string $hex, string $ignore = '')
    sodium_bin2hex(string $bin)
    
  • Constant-time string comparison:
    sodium_compare(string $str1, string $str2)

References

Thanks!

Rate this talk at joind.in/talk/5769a

Creative Commons License
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
I used reveal.js to make this presentation.