by Enrico Zimuel
Senior Software Engineer
Rogue Wave Software, Inc.
ZendCon & OpenEnterprise 2018, Las Vegas (NV), October 16
Cryptography is hard. Hard to design, hard to implement, hard to use, and hard to get right.
Attack based on information gained from the implementation of a computer system, rather than weaknesses in the implemented algorithm itself
Source: Protecting Against Side-Channel Attacks with an Ultra-Low Power Processor
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.
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;
}
We need a constant-time string comparision function
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.
A + B = C, A + C = D, A + D = E
P + P = 2P
Given P and Q find k such that Q = kP is hard!
$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
$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
$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
$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
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
$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...
$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: 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
Contact me: enrico.zimuel [at] roguewave.com
Follow me: @ezimuel