1

I tried to rebuild the the encryption flow explained here

Encryption

$password = 'pass123456';  //user password
$message = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.';

$Ku = openssl_random_pseudo_bytes(256 / 8); //user-specific symmetric key
$Ru = openssl_random_pseudo_bytes(256 / 8); //recovery code
$Su = openssl_random_pseudo_bytes(256 / 8); //user salt ###STORED ON SERVER###

$hash = hash_pbkdf2('sha256', $password, $Su, 5000, 256 / 8, true);

$split = str_split($hash, 16);
$Vu = $split[0]; //password verification token ###STORED ON SERVER###
$Ek = $split[1]; //key to encrypt Ku


$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

$Eu = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $Ek, $Ku, MCRYPT_MODE_CBC, $iv); ###STORED ON SERVER###
$Fu = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $Ru, $Ku, MCRYPT_MODE_CBC, $iv); ###STORED ON SERVER###

$encryptedMessage = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $Ku, $message, MCRYPT_MODE_CBC, $iv);

Decryption

$hash = hash_pbkdf2('sha256', ###ENTERED PASSWORD###, ###STORED Su###, 5000, 256 / 8, true);
$split = str_split($hash, 16);
$Vu = $split[0];
$Ek = $split[1];


if($Vu != ###STORED Vu###) {
   => wrong password
} else {
   => correct password
}


$Ku = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $EK, ###STORED Eu###, MCRYPT_MODE_CBC, ###STORED iv###);
$message = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $Ku, $encryptedMessage, MCRYPT_MODE_CBC, ###STORED iv###);

If I have lost my password I can recover "Ku":

$Ku = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, ###REVOVERY KEY###, ###STORED Fu###, MCRYPT_MODE_CBC, ###STORED iv###);

Is the code above secure? Is openssl_random_pseudo_bytes random enough or is there a better possibility i.e. using mouse movement to generate a initialization number?

How should the "initialization vector" for mcrypt be handled? Is this correct?

Community
  • 1
  • 1
grundig
  • 101
  • 2
  • 14

1 Answers1

1

Analysis in parts

It seems okay, but you might want to

  • Increase the iterations of your PBKDF2 function. 5000 is quite low nowadays. Try a million.
  • Rijndael with 256 bit block sizes is not as well analyzed as Rijndael with 128 bit block sizes which is AES. You should use MCRYPT_RIJNDAEL_128. This will also improve compatibility to other implementations.
  • You're not using ciphertext authentication which can detect (malicious) manipulation of your ciphertexts. See this example for a simple OpenSSL way of doing this.
  • You're using MCrypt's default zero padding which is not good if your plaintexts end in 0x00 bytes. You would need to be careful when trimming those from your decrypted message.

Is openssl_random_pseudo_bytes random enough or is there a better possibility i.e. using mouse movement to generate a initialization number?

The initialization vector needs only to be unpredictable, but not secret. There are attacks which can exploit the system if the IV generation is predictable, but openssl_random_pseudo_bytes is supposed to be good. There is a small chance that it doesn't initialize properly and you'll be using non-"strong" random bytes (check the second argument), but even then depending on your system architecture the predictability of the IV might not be exploitable.

I also wonder how you want to get mouse movements in PHP since I'm assuming this runs on a server.

Analysis as a whole

That code seems to implement the a good protocol described by Thomas Pronin, but the protocol is slightly incomplete. Because there is nothing preventing an attacker to submit a random Ru and with it overwrite all the user's values.

During account creation (and password changing) you would need to also create a verification value for the recovery key. I think it would be sufficient to create

$VRu = hash_pbkdf2('sha256', $Ru, $Su, 1000, 256 / 8, true);

and store it on the server in case of recovery.

Since Ru is randomly generated, the number of iterations can be low, but it is still a costly operation. You should limit the use of the recovery operation to reduce denial of service attacks.

Community
  • 1
  • 1
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • yes, correct it runs on a server, but I could generate it with javasript and then send it to the server – grundig Aug 19 '15 at 12:07
  • The protocol is good, but there is a problem during recovery. I've extended my answer. – Artjom B. Aug 19 '15 at 13:21
  • Is there a security problem with bin2hex(Ru) to get a readable code? – grundig Aug 20 '15 at 13:31
  • Depends on what you want to do with it. An encoding on its own has no issues. – Artjom B. Aug 20 '15 at 13:32
  • i.e. for printing it out. I suppose a user could have some problems to enter the characters from openssl_random_pseudo_bytes – grundig Aug 20 '15 at 13:34
  • I don't think a user would mind to enter 64 hex-chars during recovery, but you could use Base58 which has a higher density than Base16 (Hex), so the textual recovery code is not so long. – Artjom B. Aug 21 '15 at 17:14