Index: bakery.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/bakery/bakery.module,v
retrieving revision 1.53
diff -u -p -r1.53 bakery.module
--- bakery.module	15 Apr 2010 16:00:14 -0000	1.53
+++ bakery.module	25 Apr 2010 06:48:29 -0000
@@ -131,7 +131,7 @@ function bakery_user_update(&$edit, $acc
     $payload['uid'] = $account->uid;
     $payload['category'] = $category;
     $payload['signature'] = hash_hmac('sha256', $payload['data'] . '/' . $payload['uid'] .'/'. $payload['timestamp'], $key);
-    $payload = drupal_http_build_query(array('stroopwafel' => bakery_mix(serialize($payload), 1)));
+    $payload = drupal_http_build_query(array('stroopwafel' => bakery_mix(serialize($payload))));
     unset($_SESSION['bakery']);
     // now update the slaves
     $slaves = variable_get('bakery_slaves', array());
@@ -376,13 +376,15 @@ function _bakery_validate_cookie($type =
     return;
   }
 
-  $cookie = unserialize(bakery_mix($_COOKIE[$type], 0));
-  $signature = hash_hmac('sha256', $cookie['name'] . '/' . $cookie['mail'] . '/' . $cookie['timestamp'], $key);
-
   $valid = FALSE;
 
-  if ($signature == $cookie['signature'] && $cookie['timestamp'] + variable_get('bakery_freshness', '3600') >= $_SERVER['REQUEST_TIME']) {
-    $valid = TRUE;
+  if ($cookie_serialized = bakery_unmix($_COOKIE[$type])) {
+    $cookie = unserialize($cookie_serialized);
+
+    if ($cookie['timestamp'] + variable_get('bakery_freshness', '3600') >= $_SERVER['REQUEST_TIME']) {
+      $valid = TRUE;
+    }
+
   }
 
   return $valid ? $cookie : $valid;
@@ -547,15 +549,15 @@ function bakery_taste_stroopwafel_cookie
   $valid = FALSE;
 
   if ($payload) {
-    $cookie = unserialize(bakery_mix($payload, 0));
-    $key = variable_get('bakery_key', '');
-    $signature = hash_hmac('sha256', $cookie['data'] . '/' . $cookie['uid'] . '/' . $cookie['timestamp'], $key);
+    if ($cookie_serialized = bakery_unmix($payload)) {
+      $cookie = unserialize($cookie_serialized);
 
-    if ($signature == $cookie['signature'] && $cookie['timestamp'] + variable_get('bakery_freshness', '3600') >= $_SERVER['REQUEST_TIME']) {
-      $valid = TRUE;
-      $_SESSION['bakery'] = unserialize($cookie['data']);
-      $_SESSION['bakery']['uid'] = $cookie['uid'];
-      $_SESSION['bakery']['category'] = $cookie['category'];
+      if ($cookie['timestamp'] + variable_get('bakery_freshness', '3600') >= $_SERVER['REQUEST_TIME']) {
+        $valid = TRUE;
+        $_SESSION['bakery'] = unserialize($cookie['data']);
+        $_SESSION['bakery']['uid'] = $cookie['uid'];
+        $_SESSION['bakery']['category'] = $cookie['category'];
+      }
     }
   }
 
@@ -575,8 +577,7 @@ function _bakery_bake_chocolatechip_cook
     $cookie['master'] = variable_get('bakery_is_master', 0);
     $cookie['calories'] = 480;
     $cookie['timestamp'] = $_SERVER['REQUEST_TIME'];
-    $cookie['signature'] = hash_hmac('sha256', $cookie['name'] . '/' . $cookie['mail'] . '/' . $cookie['timestamp'], $key);
-    setcookie('CHOCOLATECHIP', bakery_mix(serialize($cookie), 1), $_SERVER['REQUEST_TIME'] + variable_get('bakery_freshness', '3600'), '/', variable_get('bakery_domain', ''));
+    setcookie('CHOCOLATECHIP', bakery_mix(serialize($cookie)), $_SERVER['REQUEST_TIME'] + variable_get('bakery_freshness', '3600'), '/', variable_get('bakery_domain', ''));
   }
 }
 
@@ -598,8 +599,7 @@ function bakery_bake_oatmeal_cookie() {
     $cookie['destination'] = $base_url .'/'. $destination;
     $cookie['calories'] = 320;
     $cookie['timestamp'] = $_SERVER['REQUEST_TIME'];
-    $cookie['signature'] = hash_hmac('sha256', $cookie['name'] . '/' . $cookie['mail'] . '/' . $cookie['timestamp'], $key);
-    setcookie('OATMEAL', bakery_mix(serialize($cookie), 1), $_SERVER['REQUEST_TIME'] + variable_get('bakery_freshness', '3600'), '/', variable_get('bakery_domain', ''));
+    setcookie('OATMEAL', bakery_mix(serialize($cookie)), $_SERVER['REQUEST_TIME'] + variable_get('bakery_freshness', '3600'), '/', variable_get('bakery_domain', ''));
   }
 
   unset($_GET['destination']);
@@ -658,34 +658,156 @@ function _bakery_eat_cookie($type = 'CHO
 }
 
 /**
- * Encryption
+ * Encrypt a string using the bakery encryption functions.
+ *
+ * See _bakery_encrypt_decrypt() for details.
  *
- * @param $text, The text that you want to encrypt.
- * @param $crypt = 1 if you want to crypt, or 0 if you want to decrypt.
+ * @param $text
+ *   The payload of data to encrypt.
+ * @param $base64encode
+ *   Optionally encode the encrypted using base64 encoding, defaults to TRUE.
  */
-function bakery_mix($text, $crypt) {
-  $key = variable_get('bakery_key', '');
-
-  $td = mcrypt_module_open('rijndael-128', '', 'ecb', '');
-  $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
+function bakery_mix($text, $base64encode = TRUE) {
+  return _bakery_encrypt_decrypt($text, TRUE, $base64encode);
+}
 
-  $key = substr($key, 0, mcrypt_enc_get_key_size($td));
+/**
+ * Decrypt a string using the bakery encryption functions.
+ *
+ * See _bakery_encrypt_decrypt() for details.
+ *
+ * @param $text
+ *   The payload of data to decrypt.
+ * @param $base64encode
+ *   Optionally decode the $text using base64 encoding, defaults to TRUE.
+ */
+function bakery_unmix($text, $base64encode = TRUE) {
+  return _bakery_encrypt_decrypt($text, FALSE, $base64encode);
+}
 
-  mcrypt_generic_init($td, $key, $iv);
+/**
+ * Bakery encryption/decryption.
+ *
+ * This function will add a IV and Message Authentication Code (MAC). Basically
+ * you can be sure that if something decrypts properly then it hasn't been
+ * tampered with either.
+ *
+ * @param $text
+ *   The text that you want to encrypt/decrypt.
+ * @param $crypt
+ *   TRUE if you want to crypt, or FALSE if you want to decrypt.
+ * @param $base64encode
+ *   Optionally encode/decode the given $text using base64 encoding.
+ * @return
+ *   FALSE on error/validation fail or the encrypted plaintext or decrypted
+ *   message. After encryption the returned string has the form:
+ *   IV | CRYPTO_TEXT | MAC
+ */
+function _bakery_encrypt_decrypt($text, $crypt, $base64encode = TRUE) {
+  $key = variable_get('bakery_key', '');
 
-  if($crypt) {
+  // If we're doing base64 encoding, need to decode if decrypting:
+  if ($base64encode && !$crypt) {
+    $text = base64_decode($text);
+  }
+
+  // We'll use AES in CTR mode
+  $td = mcrypt_module_open('rijndael-256', '', 'ctr', '');
+
+  // Compute the key:
+  $computed_key = bakery_pbkdf2($key, 'VKtl]c}~3;WJ*QEvn', 1000, mcrypt_enc_get_key_size($td));
+
+  if ($crypt) {
+    // This is encryption:
+    // Compute an IV. This IV should only EVER be used once:
+    $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
+    mcrypt_generic_init($td, $computed_key, $iv);
+    // Do the encryption:
     $encrypted_data = mcrypt_generic($td, $text);
+    // Append the IV to the data:
+    $encrypted_data = $iv . $encrypted_data;
+    // Now compute the MAC:
+    $mac = bakery_pbkdf2($encrypted_data, $computed_key, 1000, 32);
+    // Append the MAC to the IV and excrypted payload
+    $encrypted_data .= $mac;
+
   }
   else {
-    $encrypted_data = mdecrypt_generic($td, $text);
+    // This is decryption:
+    // Get the IV:
+    $iv = substr($text, 0, mcrypt_enc_get_iv_size($td));
+    // Get the MAC:
+    $message_mac = substr($text, strlen($text) - 32);
+    // Get the payload of encrypted data:
+    $encrypted_data = substr($text, mcrypt_enc_get_iv_size($td), strlen($text) - (32 + mcrypt_enc_get_iv_size($td)));
+    // Re-compute the MAC:
+    $mac = bakery_pbkdf2($iv . $encrypted_data, $computed_key, 1000, 32);
+
+    // Make sure that MAC in the message ($message_mac) matches the computed $mac
+    if ($mac != $message_mac) {
+      return FALSE;
+    }
+
+    // Now do the decryption:
+    mcrypt_generic_init($td, $computed_key, $iv);
+    $encrypted_data = mdecrypt_generic($td, $encrypted_data);
+
   }
 
-  mcrypt_generic_deinit($td);
-  mcrypt_module_close($td);
+  // If we're doing base64 encoding, need to encode if encrypting:
+  if ($base64encode && $crypt) {
+    $encrypted_data = base64_encode($encrypted_data);
+  }
 
   return $encrypted_data;
 }
 
+
+/**
+ * PBKDF2 Implementation (as described in RFC 2898);
+ *
+ * Slightly modified version of the code at:
+ * http://www.itnewb.com/v/PHP-Encryption-Decryption-Using-the-MCrypt-Library-libmcrypt
+ *
+ *  @param $password.
+ *  @param $salt
+ *  @param $iteration_count
+ *    Iteration count (use 1000 or higher.)
+ *  @param $key_length
+ *    Derived key length.
+ *  @param $a
+ *    Hash algorithm.
+ *
+ *  @return
+ *    Derived key
+*/
+function bakery_pbkdf2($password, $salt, $iteration_count, $key_length, $a = 'sha256') {
+
+  $hl = strlen(hash($a, NULL, TRUE)); // Hash length
+  $kb = ceil($key_length / $hl);      // Key blocks to compute
+  $dk = '';                           // Derived key
+
+  // Create key
+  for ($block = 1; $block <= $kb; $block++) {
+
+    // Initial hash for this block
+    $ib = $b = hash_hmac($a, $salt . pack('N', $block), $password, TRUE);
+
+    // Perform block iterations
+    for ($i = 1; $i < $iteration_count; $i++) {
+
+      // XOR each iterate
+      $ib ^= ($b = hash_hmac($a, $b, $password, TRUE));
+
+    }
+
+    $dk .= $ib; // Append iterated block
+  }
+
+  // Return derived key of correct length
+  return substr($dk, 0, $key_length);
+}
+
 /**
  * Perform standard Drupal login operations for a user object.
  *
@@ -804,7 +926,7 @@ function bakery_uncrumble($form, &$form_
     ->where("LOWER(u.mail) = LOWER(:mail)", array(':mail' => $cookie['mail']));
   $result = $query->execute();
   $samemail = $result->fetchObject();
-    
+
   $query = db_select('users', 'u')
     ->fields('u', array('uid', 'name', 'mail'))
     ->condition('u.uid', 0, '!=')
