Index: wp-login.php
===================================================================
--- wp-login.php	(revision 25187)
+++ wp-login.php	(working copy)
@@ -197,7 +197,7 @@
  * @return bool|WP_Error True: when finish. WP_Error on error
  */
 function retrieve_password() {
-	global $wpdb, $current_site;
+	global $wpdb, $current_site, $wp_hasher;
 
 	$errors = new WP_Error();
 
@@ -236,14 +236,17 @@
 	else if ( is_wp_error($allow) )
 		return $allow;
 
-	$key = $wpdb->get_var($wpdb->prepare("SELECT user_activation_key FROM $wpdb->users WHERE user_login = %s", $user_login));
-	if ( empty($key) ) {
-		// Generate something random for a key...
-		$key = wp_generate_password(20, false);
-		do_action('retrieve_password_key', $user_login, $key);
-		// Now insert the new md5 key into the db
-		$wpdb->update($wpdb->users, array('user_activation_key' => $key), array('user_login' => $user_login));
+	// Generate something random for a key...
+	$key = wp_generate_password(20, false);
+	do_action('retrieve_password_key', $user_login, $key);
+	// Now insert the key, hashed, into the db
+	if ( empty( $wp_hasher ) ) {
+		require_once ABSPATH . 'wp-includes/class-phpass.php';
+		$wp_hasher = new PasswordHash( 8, true );
 	}
+	$hashed = $wp_hasher->HashPassword( $key );
+	$wpdb->update( $wpdb->users, array( 'user_activation_key' => $hashed ), array( 'user_login' => $user_login ) );
+
 	$message = __('Someone requested that the password be reset for the following account:') . "\r\n\r\n";
 	$message .= network_home_url( '/' ) . "\r\n\r\n";
 	$message .= sprintf(__('Username: %s'), $user_login) . "\r\n\r\n";
@@ -276,10 +279,10 @@
  *
  * @param string $key Hash to validate sending user's password
  * @param string $login The user login
- * @return object|WP_Error User's database row on success, error object for invalid keys
+ * @return WP_User|WP_Error WP_User object on success, WP_Error object for invalid keys
  */
 function check_password_reset_key($key, $login) {
-	global $wpdb;
+	global $wpdb, $wp_hasher;
 
 	$key = preg_replace('/[^a-z0-9]/i', '', $key);
 
@@ -289,12 +292,22 @@
 	if ( empty($login) || !is_string($login) )
 		return new WP_Error('invalid_key', __('Invalid key'));
 
-	$user = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->users WHERE user_activation_key = %s AND user_login = %s", $key, $login));
-
-	if ( empty( $user ) )
+	$row = $wpdb->get_row( $wpdb->prepare( "SELECT ID, user_activation_key FROM $wpdb->users WHERE user_login = %s", $login ) );
+	if ( ! $row )
 		return new WP_Error('invalid_key', __('Invalid key'));
 
-	return $user;
+	if ( empty( $wp_hasher ) ) {
+		require_once ABSPATH . 'wp-includes/class-phpass.php';
+		$wp_hasher = new PasswordHash( 8, true );
+	}
+
+	if ( $wp_hasher->CheckPassword( $key, $row->user_activation_key ) )
+		return get_userdata( $row->ID );
+
+	if ( $key === $row->user_activation_key )
+		return new WP_Error('expired_key', __('Invalid key'));
+
+	return new WP_Error('invalid_key', __('Invalid key'));
 }
 
 /**
@@ -440,7 +453,13 @@
 		}
 	}
 
-	if ( isset($_GET['error']) && 'invalidkey' == $_GET['error'] ) $errors->add('invalidkey', __('Sorry, that key does not appear to be valid.'));
+	if ( isset( $_GET['error'] ) ) {
+		if ( 'invalidkey' == $_GET['error'] )
+			$errors->add( 'invalidkey', __( 'Sorry, that key does not appear to be valid.' ) );
+		elseif ( 'expiredkey' == $_GET['error'] )
+			$errors->add( 'expiredkey', __( 'Sorry, that key has expired. Please try again.' ) );
+	}
+
 	$redirect_to = apply_filters( 'lostpassword_redirect', !empty( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '' );
 
 	do_action('lost_password');
@@ -476,7 +495,10 @@
 	$user = check_password_reset_key($_GET['key'], $_GET['login']);
 
 	if ( is_wp_error($user) ) {
-		wp_redirect( site_url('wp-login.php?action=lostpassword&error=invalidkey') );
+		if ( $user->get_error_code() === 'expired_key' )
+			wp_redirect( site_url( 'wp-login.php?action=lostpassword&error=expiredkey' ) );
+		else
+			wp_redirect( site_url( 'wp-login.php?action=lostpassword&error=invalidkey' ) );
 		exit;
 	}
 
