Index: includes/passwordhash.inc
===================================================================
RCS file: includes/passwordhash.inc
diff -N includes/passwordhash.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/passwordhash.inc	21 Feb 2008 02:55:16 -0000
@@ -0,0 +1,214 @@
+<?php
+// $Id$
+/**
+ * Portable PHP password hashing framework.
+ *
+ * Version 0.1 / Drupal.
+ *
+ * Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
+ * the public domain.
+ *
+ * There's absolutely no warranty.
+ *
+ * The homepage URL for this framework is:
+ *
+ *  http://www.openwall.com/phpass/
+ *
+ * This modified version and other file contents licensed under the GPL.
+ * See COPYRIGHT.txt and LICENSE.txt.
+ */
+
+/**
+ * The standard log2 number of iterations for password streching.
+ */ 
+define('DRUPAL_HASH_ITERATION', 14);
+
+class PasswordHash {
+  var $itoa64;
+  var $iteration_count_log2;
+  var $portable_hashes;
+  var $random_state;
+
+  function PasswordHash($iteration_count_log2 = DRUPAL_HASH_ITERATION) {
+    $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+    if ($iteration_count_log2 < 14 || $iteration_count_log2 > 31) {
+      $iteration_count_log2 = 14;
+    }
+    $this->iteration_count_log2 = $iteration_count_log2;
+
+    $this->random_state = microtime() . getmypid();
+  }
+
+  function get_random_bytes($count)  {
+    $output = '';
+    if (($fh = @fopen('/dev/urandom', 'rb'))) {
+      $output = fread($fh, $count);
+      fclose($fh);
+    }
+
+    if (strlen($output) < $count) {
+      $output = '';
+      for ($i = 0; $i < $count; $i += 16) {
+        $this->random_state = md5(microtime() . $this->random_state);
+        $output .= md5($this->random_state, TRUE);
+      }
+      $output = substr($output, 0, $count);
+    }
+
+    return $output;
+  }
+
+  function encode64($input, $count)  {
+    $output = '';
+    $i = 0;
+    do {
+      $value = ord($input[$i++]);
+      $output .= $this->itoa64[$value & 0x3f];
+      if ($i < $count) {
+        $value |= ord($input[$i]) << 8;
+      }
+      $output .= $this->itoa64[($value >> 6) & 0x3f];
+      if ($i++ >= $count) {
+        break;
+      }
+      if ($i < $count) {
+        $value |= ord($input[$i]) << 16;
+      }
+      $output .= $this->itoa64[($value >> 12) & 0x3f];
+      if ($i++ >= $count) {
+        break;
+      }
+      $output .= $this->itoa64[($value >> 18) & 0x3f];
+    } while ($i < $count);
+
+    return $output;
+  }
+
+  function gensalt_private($input) {
+    $output = '$P$';
+    $output .= $this->itoa64[min($this->iteration_count_log2, 30)];
+    $output .= $this->encode64($input, 6);
+
+    return $output;
+  }
+
+  function crypt_private($password, $setting)  {
+    $output = '*';
+
+    if (substr($setting, 0, 3) != '$P$') {
+      return $output;
+    }
+
+    $count_log2 = $this->GetHashCountLog2($setting);
+    if ($count_log2 < 7 || $count_log2 > 30) {
+      return $output;
+    }
+
+    $count = 1 << $count_log2;
+
+    $salt = substr($setting, 4, 8);
+    if (strlen($salt) != 8) {
+      return $output;
+    }
+
+    // We're kind of forced to use MD5 here since it's the only
+    // cryptographic primitive available in all versions of PHP
+    // currently in use.  To implement our own low-level crypto
+    // in PHP would result in much worse performance and
+    // consequently in lower iteration counts and hashes that are
+    // quicker to crack (by non-PHP code).
+
+    $hash = md5($salt . $password, TRUE);
+    do {
+      $hash = md5($hash . $password, TRUE);
+    } while (--$count);
+
+    $output = substr($setting, 0, 12);
+    $output .= $this->encode64($hash, 16);
+
+    return $output;
+  }
+
+  function HashPassword($password) {
+    $random = '';
+
+    if (strlen($random) < 6) {
+      $random = $this->get_random_bytes(6);
+    }
+    $hash = $this->crypt_private($password, $this->gensalt_private($random));
+    if (strlen($hash) == 34) {
+      return $hash;
+    }
+
+    // Returning '*' on error is safe here, but would _not_ be safe
+    // in a crypt(3)-like function used _both_ for generating new
+    // hashes and for validating passwords against existing hashes.
+    return '*';
+  }
+
+  function CheckPassword($password, $stored_hash) {
+    return $stored_hash == $this->crypt_private($password, $stored_hash);
+  }
+  
+  function GetHashCountLog2($stored_hash) {
+    return strpos($this->itoa64, $stored_hash[3]);
+  }
+}
+
+/**
+ * Hash a password using a secure hash.
+ *
+ * @param $password
+ *   A plain-text password
+ *
+ * @return
+ *   A string containing the hashed password (and a salt), or FALSE on failure.
+ */
+function user_hash_password($password) {
+  $ph = new PasswordHash();
+  $new_hash = $ph->HashPassword($password);
+  return ($new_hash[0] == '*') ? FALSE : $new_hash;
+}
+
+/**
+ * Check whether a plain text password matches a stored hashed password.
+ *
+ * @param $password
+ *   A plain-text password
+ * @param $account
+ *   A user object with at least the fields from the {users} table.
+ *
+ * @return
+ *   TRUE or FALSE.
+ */
+function user_check_password($password, $account) {
+  if (strlen($account->pass) <= 32 || (substr($account->pass, 0, 3) != '$P$')) {
+    // This may be an updated password that was re-hashed with MD5 in
+    // system_update_7001().
+    return $account->pass == md5($account->init . $account->created . md5($password));
+  }
+  else {
+    $ph = new PasswordHash();
+    return $ph->CheckPassword($password, $account->pass);
+  }
+}
+
+/**
+ * Check whether a user's hashed password needs to be replaced with a new hash.
+ *
+ * @param $account
+ *   A user object with at least the fields from the {users} table.
+ *
+ * @return
+ *   TRUE or FALSE.
+ */
+function user_needs_rehash($account) {
+  // Check whether this was an updated password - i.e. still a normal md5().
+  if (strlen($account->pass) <= 32 || (substr($account->pass, 0, 3) != '$P$')) {
+    return TRUE;
+  }
+  // Check whether the iteration count used is less than the standard number.
+  $ph = new PasswordHash();
+  return ($ph->GetHashCountLog2($account->pass) < DRUPAL_HASH_ITERATION);
+}
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.239
diff -u -p -r1.239 system.install
--- modules/system/system.install	20 Feb 2008 13:46:41 -0000	1.239
+++ modules/system/system.install	21 Feb 2008 02:55:17 -0000
@@ -2505,6 +2505,20 @@ function system_update_7000() {
 }
 
 /**
+ * Increase the length of the password field to accomodate better hashes.
+ *
+ * Also re-hashes all current passwords with CONCAT(init, created) as a salt. 
+ * This provides a very modest immediate improvement in security.
+ */
+function system_update_7001() {
+  $ret = array();
+  db_change_field($ret, 'users', 'pass', 'pass', array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
+  // Rehash with a salt all current hashed passwords.
+  $ret[] = update_sql("UPDATE {users} SET pass = MD5(CONCAT(CONCAT(init, created), pass)) WHERE uid > 0");
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/user/user.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.install,v
retrieving revision 1.6
diff -u -p -r1.6 user.install
--- modules/user/user.install	18 Feb 2008 16:53:36 -0000	1.6
+++ modules/user/user.install	21 Feb 2008 02:55:17 -0000
@@ -144,10 +144,10 @@ function user_schema() {
       ),
       'pass' => array(
         'type' => 'varchar',
-        'length' => 32,
+        'length' => 128,
         'not null' => TRUE,
         'default' => '',
-        'description' => t("User's password (md5 hash)."),
+        'description' => t("User's password (hashed)."),
       ),
       'mail' => array(
         'type' => 'varchar',
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.896
diff -u -p -r1.896 user.module
--- modules/user/user.module	20 Feb 2008 13:46:43 -0000	1.896
+++ modules/user/user.module	21 Feb 2008 02:55:17 -0000
@@ -157,7 +157,7 @@ function user_load($array = array()) {
     }
     else if ($key == 'pass') {
       $query[] = "pass = '%s'";
-      $params[] = md5($value);
+      $params[] = $value;
     }
     else {
       $query[]= "LOWER($key) = LOWER('%s')";
@@ -214,7 +214,9 @@ function user_save($account, $array = ar
   $user_fields = $table['fields'];
 
   if (!empty($array['pass'])) {
-    $array['pass'] = md5($array['pass']);
+    // Allow alternate hashing schemes.
+    require_once variable_get('passwordhash.inc', './includes/passwordhash.inc');
+    $array['pass'] = user_hash_password(trim($array['pass']));
   }
   else {
     // Avoid overwriting an existing password with a blank password.
@@ -1283,12 +1285,26 @@ function user_login_final_validate($form
 function user_authenticate($form_values = array()) {
   global $user;
 
+  $password = trim($form_values['pass']);
   // Name and pass keys are required.
-  if (!empty($form_values['name']) && !empty($form_values['pass']) &&
-      $account = user_load(array('name' => $form_values['name'], 'pass' => trim($form_values['pass']), 'status' => 1))) {
-    $user = $account;
-    user_authenticate_finalize($form_values);
-    return $user;
+  if (!empty($form_values['name']) && !empty($password)) {
+    $account = db_fetch_object(db_query("SELECT * FROM {users} WHERE name = '%s' AND status = 1", $form_values['name']));
+    if ($account) {
+      // Allow alternate hashing schemes.
+      require_once variable_get('passwordhash.inc', './includes/passwordhash.inc');
+      if (user_check_password($password, $account)) {
+        if (user_needs_rehash($account)) {
+           $new_hash = user_hash_password($password);
+           if ($new_hash) {
+             db_query("UPDATE {users} SET pass = '%s' WHERE uid = %d", $new_hash, $account->uid);
+           }
+        }
+        $account = user_load(array('uid' => $account->uid, 'status' => 1));
+        $user = $account;
+        user_authenticate_finalize($form_values);
+        return $user;
+      }
+    }
   }
 }
 
