Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.115
diff -u -u -p -r1.115 system.install
--- modules/system/system.install	22 May 2007 05:52:17 -0000	1.115
+++ modules/system/system.install	28 May 2007 14:28:27 -0000
@@ -599,7 +599,7 @@ function system_install() {
       db_query("CREATE TABLE {users} (
         uid int unsigned NOT NULL default '0',
         name varchar(60) NOT NULL default '',
-        pass varchar(32) NOT NULL default '',
+        pass varchar(255) NOT NULL default '',
         mail varchar(64) default '',
         mode tinyint NOT NULL default '0',
         sort tinyint default '0',
@@ -4093,6 +4093,15 @@ function system_update_6018() {
 }
 
 /**
+ * Make {user}'s password field larger to accomdate more secure hashes
+ */
+function system_update_6019() {
+  $ret = array();
+  $ret = update_sql("ALTER TABLE {users} ALTER COLUMN pass VARCHAR(255) NOT NULL default ''");
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-5.x-to-6.x"
  * The next series of updates should start at 7000.
  */
Index: modules/user/phpass.inc
===================================================================
RCS file: modules/user/phpass.inc
diff -N modules/user/phpass.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/user/phpass.inc	28 May 2007 14:28:27 -0000
@@ -0,0 +1,247 @@
+<?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/
+#
+# Please be sure to update the Version line if you edit this file in any way.
+# It is suggested that you leave the main version number intact, but indicate
+# your project name (after the slash) and add your own revision information.
+#
+# Please do not change the "private" password hashing method implemented in
+# here, thereby making your hashes incompatible.  However, if you must, please
+# change the hash type identifier (the "$P$") to something different.
+#
+# Obviously, since this code is in the public domain, the above are not
+# requirements (there can be none), but merely suggestions.
+#
+class PasswordHash {
+  var $itoa64;
+  var $iteration_count_log2;
+  var $portable_hashes;
+  var $random_state;
+
+  function PasswordHash($iteration_count_log2, $portable_hashes)
+  {
+    $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
+    if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
+      $iteration_count_log2 = 8;
+    $this->iteration_count_log2 = $iteration_count_log2;
+
+    $this->portable_hashes = $portable_hashes;
+
+    $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 .=
+            pack('H*', md5($this->random_state));
+      }
+      $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 +
+      ((PHP_VERSION >= '5') ? 5 : 3), 30)];
+    $output .= $this->encode64($input, 6);
+
+    return $output;
+  }
+
+  function crypt_private($password, $setting)
+  {
+    $output = '*0';
+    if (substr($setting, 0, 2) == $output)
+      $output = '*1';
+
+    if (substr($setting, 0, 3) != '$P$')
+      return $output;
+
+    $count_log2 = strpos($this->itoa64, $setting[3]);
+    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).
+    if (PHP_VERSION >= '5') {
+      $hash = md5($salt . $password, TRUE);
+      do {
+        $hash = md5($hash . $password, TRUE);
+      } while (--$count);
+    } else {
+      $hash = pack('H*', md5($salt . $password));
+      do {
+        $hash = pack('H*', md5($hash . $password));
+      } while (--$count);
+    }
+
+    $output = substr($setting, 0, 12);
+    $output .= $this->encode64($hash, 16);
+
+    return $output;
+  }
+
+  function gensalt_extended($input)
+  {
+    $count_log2 = min($this->iteration_count_log2 + 8, 24);
+    # This should be odd to not reveal weak DES keys, and the
+    # maximum valid value is (2**24 - 1) which is odd anyway.
+    $count = (1 << $count_log2) - 1;
+
+    $output = '_';
+    $output .= $this->itoa64[$count & 0x3f];
+    $output .= $this->itoa64[($count >> 6) & 0x3f];
+    $output .= $this->itoa64[($count >> 12) & 0x3f];
+    $output .= $this->itoa64[($count >> 18) & 0x3f];
+
+    $output .= $this->encode64($input, 3);
+
+    return $output;
+  }
+
+  function gensalt_blowfish($input)
+  {
+    # This one needs to use a different order of characters and a
+    # different encoding scheme from the one in encode64() above.
+    # We care because the last character in our encoded string will
+    # only represent 2 bits.  While two known implementations of
+    # bcrypt will happily accept and correct a salt string which
+    # has the 4 unused bits set to non-zero, we do not want to take
+    # chances and we also do not want to waste an additional byte
+    # of entropy.
+    $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+
+    $output = '$2a$';
+    $output .= chr(ord('0') + $this->iteration_count_log2 / 10);
+    $output .= chr(ord('0') + $this->iteration_count_log2 % 10);
+    $output .= '$';
+
+    $i = 0;
+    do {
+      $c1 = ord($input[$i++]);
+      $output .= $itoa64[$c1 >> 2];
+      $c1 = ($c1 & 0x03) << 4;
+      if ($i >= 16) {
+        $output .= $itoa64[$c1];
+        break;
+      }
+
+      $c2 = ord($input[$i++]);
+      $c1 |= $c2 >> 4;
+      $output .= $itoa64[$c1];
+      $c1 = ($c2 & 0x0f) << 2;
+
+      $c2 = ord($input[$i++]);
+      $c1 |= $c2 >> 6;
+      $output .= $itoa64[$c1];
+      $output .= $itoa64[$c2 & 0x3f];
+    } while (1);
+
+    return $output;
+  }
+
+  function HashPassword($password)
+  {
+    $random = '';
+
+    if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) {
+      $random = $this->get_random_bytes(16);
+      $hash =
+          crypt($password, $this->gensalt_blowfish($random));
+      if (strlen($hash) == 60)
+        return $hash;
+    }
+
+    if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) {
+      if (strlen($random) < 3)
+        $random = $this->get_random_bytes(3);
+      $hash =
+          crypt($password, $this->gensalt_extended($random));
+      if (strlen($hash) == 20)
+        return $hash;
+    }
+
+    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)
+  {
+    $hash = $this->crypt_private($password, $stored_hash);
+    if ($hash[0] == '*')
+      $hash = crypt($password, $stored_hash);
+
+    return $hash == $stored_hash;
+  }
+}
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.785
diff -u -u -p -r1.785 user.module
--- modules/user/user.module	22 May 2007 05:52:17 -0000	1.785
+++ modules/user/user.module	28 May 2007 14:28:27 -0000
@@ -94,11 +94,7 @@ function user_load($array = array()) {
       $query[] = "$key = %d";
       $params[] = $value;
     }
-    else if ($key == 'pass') {
-      $query[] = "pass = '%s'";
-      $params[] = md5($value);
-    }
-    else {
+    else
       $query[]= "LOWER($key) = LOWER('%s')";
       $params[] = $value;
     }
@@ -158,7 +154,7 @@ function user_save($account, $array = ar
     foreach ($array as $key => $value) {
       if ($key == 'pass' && !empty($value)) {
         $query .= "$key = '%s', ";
-        $v[] = md5($value);
+        $v[] = _user_get_hash($value);
       }
       else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
         if (in_array($key, $user_fields)) {
@@ -223,7 +219,7 @@ function user_save($account, $array = ar
       switch ($key) {
         case 'pass':
           $fields[] = $key;
-          $values[] = md5($value);
+          $values[] = _user_get_hash($value);
           $s[] = "'%s'";
           break;
         case 'uid':        case 'mode':     case 'sort':
@@ -1079,7 +1075,13 @@ function user_login_validate($form_value
     else if ($form_values['pass']) {
       $user = user_authenticate($form_values['name'], trim($form_values['pass']));
 
-      if (!$user->uid) {
+      if ($user->uid) {
+        // replace md5 password hash with more secure hash
+        if ($user->pass[0] != '$') {
+          user_save($user, array('pass' => trim($form_values['pass'])));
+        }
+      }
+      else {
         form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password'))));
         watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_values['name']));
       }
@@ -1107,9 +1109,11 @@ function user_authenticate($name, $pass)
   global $user;
 
   // Try to log in the user locally. Don't set $user unless successful.
-  if ($account = user_load(array('name' => $name, 'pass' => $pass, 'status' => 1))) {
-    $user = $account;
-    return $user;
+  if ($account = user_load(array('name' => $name, 'status' => 1))) {
+    if (_user_valid_hash($pass, $account->pass)) {
+      $user = $account;
+      return $user;
+    }
   }
 
   // Strip name and server from ID:
@@ -1278,7 +1282,7 @@ function user_pass_reset_url($account) {
 }
 
 function user_pass_rehash($password, $timestamp, $login) {
-  return md5($timestamp . $password . $login);
+  return _user_get_hash($timestamp . $password . $login);
 }
 
 function user_register() {
@@ -2698,6 +2702,14 @@ function user_admin_settings() {
     '#default_value' => variable_get('user_picture_guidelines', ''),
     '#description' => t("This text is displayed at the picture upload form in addition to the default guidelines. It's useful for helping or instructing your users."),
   );
+  //
+  $form['user_hash_strength'] = array(
+    '#type' => 'select',
+    '#title' => t('Password Hash Strength'),
+    '#default_value' => variable_get('user_hash_strength', 8),
+    '#options' => drupal_map_assoc(range(8, 31)),
+    '#description' => t('Select a higher number for a more secure password hash.'),
+  );
 
   return system_settings_form($form);
 }
@@ -3161,3 +3173,18 @@ function _user_mail_notify($op, $account
   }
   return $result;
 }
+
+function _user_get_hash($pass) {
+  include_once './'. drupal_get_path('module', 'user') .'/phpass.inc';
+  $phpass = new PasswordHash(variable_get('user_hash_strength', 8), TRUE);
+  return $phpass->HashPassword($pass);
+}
+
+function _user_valid_hash($pass, $hash) {
+  if ($hash[0] == '$') {
+    include_once './'. drupal_get_path('module', 'user') .'/phpass.inc';
+    $phpass = new PasswordHash(variable_get('user_hash_strength', 8), TRUE);
+    return $phpass->CheckPassword($pass, $hash);
+  }
+  return md5($pass) == $hash;
+}
