1

This is a question related to security posted here.

I am using this in my current PHP 7.0 setup which works fine. But because mcrypt has been replaced with openssl since 7.2, I am working to update the encrypt and decrypt functions with the ones posted here since it's built-in.

But because this is on a webpage with 25 items, it's taking a lot of time to execute which is unacceptable to the end-user.

<?php
define("ENCRYPTION_KEY", "!@#$%^&*");

$StartTime = microtime(TRUE);

$e = [];
for ($i = 0; $i < 25; $i++)
{
    $e[] = encrypt($i, ENCRYPTION_KEY);
}

echo number_format(microtime(TRUE) - $StartTime, 3)." seconds\n";

# https://stackoverflow.com/a/50373095/126833
function sign($message, $key) {
    return hash_hmac('sha256', $message, $key) . $message;
}

function verify($bundle, $key) {
    return hash_equals(
      hash_hmac('sha256', mb_substr($bundle, 64, null, '8bit'), $key),
      mb_substr($bundle, 0, 64, '8bit')
    );
}

function getKey($password, $keysize = 16) {
    return hash_pbkdf2('sha256',$password,'some_token',100000,$keysize,true);
}

function encrypt($message, $password) {
    $iv = random_bytes(16);
    $key = getKey($password);
    $result = sign(openssl_encrypt($message,'aes-256-ctr',$key,OPENSSL_RAW_DATA,$iv), $key);
    return bin2hex($iv).bin2hex($result);
}

function decrypt($hash, $password) {
    $iv = hex2bin(substr($hash, 0, 32));
    $data = hex2bin(substr($hash, 32));
    $key = getKey($password);
    if (!verify($data, $key)) {
      return null;
    }
    return openssl_decrypt(mb_substr($data, 64, null, '8bit'),'aes-256-ctr',$key,OPENSSL_RAW_DATA,$iv);
}
?>

.

$ php encrypt-decrypt.php 
6.288 seconds

Is there any way to execute this real fast ? (Like less than a second for 25 iterations)

Alex K.
  • 171,639
  • 30
  • 264
  • 288
anjanesh
  • 3,771
  • 7
  • 44
  • 58
  • 3
    You tell hash_pbkdf2 to perform 100k iterations, this is where the time is taken & its doing what its supposed to by taking that time. To speed it up reduce the iteration count to balance performance. – Alex K. Jul 23 '18 at 12:23
  • 1
    I can see that the example in the doc (http://php.net/manual/en/function.hash-pbkdf2.php) uses 1000 iterations which is much faster. I blindly copied what was posted in the other SO answer. Thanks. – anjanesh Jul 23 '18 at 12:31
  • 1
    Remember that more iterations means more security for your users. That's why @AlexK. used the word "balance". Perhaps 10k iterations is acceptable performance. – President James K. Polk Jul 23 '18 at 12:52
  • @JamesKPolk Did you notice where `getKey / hash_pbkdf2` is called? – Maarten Bodewes Jul 23 '18 at 13:44
  • @MaartenBodewes: No, I don't even like reading PHP. – President James K. Polk Jul 23 '18 at 15:06

1 Answers1

3

The number of iterations of PBKDF2 is called the work factor. If the number of iterations is high then the work factor, the amount of work that PBKDF2 needs to perform, is high as well. Note that the number of iterations is linear with the amount of work to be performed, and computers get faster (close to) exponentially.

Note that PBKDF2 implementations can be sped up both in hardware and software. So beware that an attacker will be able to execute them faster. 100,000 is actually a recommended amount.


Your implementation correctly calls PBKDF2 for a small amount of bytes (smaller than the output of the hash value. If you do not do this then PBKDF2 is basically called twice, while the attacker may get away with calling it just once to verify a password guess. So beware that you don't change the code to ask for two keys, which would only slow your server down.

However, you call $key = getKey($password); within the encrypt function. This means that the key is derived for each call. This is of course not very smart: you should cache the key until it is not needed anymore, and destroy it directly thereafter. There is no need to derive the key 25 times. Just lowering the iteration is a makeshift solution that hands back a lot of advantage to an attacker.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • 1
    Yes, that's much faster even with 100000 iterations. I put $key = getKey(ENCRYPTION_KEY); out of the loop and it executed in 0.259 seconds. Thanks. – anjanesh Jul 23 '18 at 14:32