Wednesday, July 10, 2013

CakePHP: Using the IV as the Key

A previous post highlighted CakePHP's abysmal homebrew cipher. They realized it was weak and attempted to fix it by using the Rijdnael cipher instead. They even used CBC mode. But instead of generating a random IV for every encryption, they used the key as the IV. This is really bad, because you can easily recover the key with a chosen ciphertext or chosen plaintext attack.

They realized that was a bad idea, and tried to switch to randomly-generated IVs in a backwards compatible way. Unfortunately, the way they did it makes 1 in every  216 of the old ciphertexts undecryptable.

They are also using zero-byte padding, just stripping the trailing zero bytes after decryption. This is ambiguous: If the original message ends in zero bytes, they will be stripped off after the decryption, and that part of the message will be lost.

 public static function rijndael($text, $key, $operation) {  
      if (empty($key)) {  
           trigger_error(__d('cake_dev', 'You cannot use an empty key for Security::rijndael()'), E_USER_WARNING);  
           return '';  
      }  
      if (empty($operation) || !in_array($operation, array('encrypt', 'decrypt'))) {  
           trigger_error(__d('cake_dev', 'You must specify the operation for Security::rijndael(), either encrypt or decrypt'), E_USER_WARNING);  
           return '';  
      }  
      if (strlen($key) < 32) {  
           trigger_error(__d('cake_dev', 'You must use a key larger than 32 bytes for Security::rijndael()'), E_USER_WARNING);  
           return '';  
      }  
      $algorithm = MCRYPT_RIJNDAEL_256;  
      $mode = MCRYPT_MODE_CBC;  
      $ivSize = mcrypt_get_iv_size($algorithm, $mode);  
      $cryptKey = substr($key, 0, 32);  
      if ($operation === 'encrypt') {  
           $iv = mcrypt_create_iv($ivSize, MCRYPT_RAND);  
           return $iv . '$$' . mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);  
      }  
      // Backwards compatible decrypt with fixed iv  
      if (substr($text, $ivSize, 2) !== '$$') {  
           $iv = substr($key, strlen($key) - 32, 32);  
           return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");  
      }  
      $iv = substr($text, 0, $ivSize);  
      $text = substr($text, $ivSize + 2);  
      return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");  
 }  

Lessons learned:
  • Always use unambiguous encodings, i.e. don't rely on low probability of random data containing your separator, and use unambiguous padding.
  • Don't use the key as anything other than the key. If you do, you're almost certainly leaking information about it.
  • Always use an IV that is randomly generated (from a secure CSPRNG) at the time of encryption.

3 comments:

  1. Fun fact: MCRYPT_RIJNDAEL_256 is not in fact the same as AES-256 and there is really no good reason to ever use it.

    ReplyDelete
  2. CakePHP is superior across web development platforms it is offering most effective solutions. In this term, CakePHP has made this thing simpler for the programmers in order to develop dynamic web applications.

    ReplyDelete