Index: wp-activate.php
===================================================================
--- wp-activate.php	(revision 41829)
+++ wp-activate.php	(working copy)
@@ -81,6 +81,10 @@
 			    <label for="key"><?php _e('Activation Key:') ?></label>
 			    <br /><input type="text" name="key" id="key" value="" size="50" />
 			</p>
+			<p>
+			    <label for="key"><?php _e( 'Signup ID:' ) ?></label>
+			    <br /><input type="number" name="signup_id" id="signup_id" value="" size="50" />
+			</p>
 			<p class="submit">
 			    <input id="submit" type="submit" name="Submit" class="submit" value="<?php esc_attr_e('Activate') ?>" />
 			</p>
@@ -88,8 +92,9 @@
 
 	<?php } else {
 
-		$key = !empty($_GET['key']) ? $_GET['key'] : $_POST['key'];
-		$result = wpmu_activate_signup( $key );
+		$key = ! empty( $_GET['key'] ) ? $_GET['key'] : $_POST['key'];
+		$signup_id = ! empty( $_GET['signup_id'] ) ? $_GET['signup_id'] : $_POST['signup_id'];
+		$result = wpmu_activate_signup( $key, $signup_id );
 		if ( is_wp_error($result) ) {
 			if ( 'already_active' == $result->get_error_code() || 'blog_taken' == $result->get_error_code() ) {
 				$signup = $result->get_error_data();
Index: wp-admin/user-new.php
===================================================================
--- wp-admin/user-new.php	(revision 41829)
+++ wp-admin/user-new.php	(working copy)
@@ -158,8 +158,8 @@
 			}
 			wpmu_signup_user( $new_user_login, $new_user_email, array( 'add_to_blog' => get_current_blog_id(), 'new_role' => $_REQUEST['role'] ) );
 			if ( isset( $_POST[ 'noconfirmation' ] ) && current_user_can( 'manage_network_users' ) ) {
-				$key = $wpdb->get_var( $wpdb->prepare( "SELECT activation_key FROM {$wpdb->signups} WHERE user_login = %s AND user_email = %s", $new_user_login, $new_user_email ) );
-				$new_user = wpmu_activate_signup( $key );
+				$row = $wpdb->get_row( $wpdb->prepare( "SELECT activation_key, signup_id FROM {$wpdb->signups} WHERE user_login = %s AND user_email = %s", $new_user_login, $new_user_email ) );
+				$new_user = wpmu_activate_signup( $row['activation_key'], $row['signup_id'] );
 				if ( is_wp_error( $new_user ) ) {
 					$redirect = add_query_arg( array( 'update' => 'addnoconfirmation' ), 'user-new.php' );
 				} elseif ( ! is_user_member_of_blog( $new_user['user_id'] ) ) {
Index: wp-includes/ms-default-filters.php
===================================================================
--- wp-includes/ms-default-filters.php	(revision 41829)
+++ wp-includes/ms-default-filters.php	(working copy)
@@ -26,7 +26,7 @@
 add_action( 'wpmu_new_user', 'newuser_notify_siteadmin' );
 add_action( 'wpmu_activate_user', 'add_new_user_to_blog', 10, 3 );
 add_action( 'wpmu_activate_user', 'wpmu_welcome_user_notification', 10, 3 );
-add_action( 'after_signup_user', 'wpmu_signup_user_notification', 10, 4 );
+add_action( 'after_signup_user', 'wpmu_signup_user_notification', 10, 5 );
 add_action( 'network_site_new_created_user',   'wp_send_new_user_notifications' );
 add_action( 'network_site_users_created_user', 'wp_send_new_user_notifications' );
 add_action( 'network_user_new_created_user',   'wp_send_new_user_notifications' );
@@ -40,7 +40,7 @@
 add_action( 'wpmu_new_blog', 'wpmu_log_new_registrations', 10, 2 );
 add_action( 'wpmu_new_blog', 'newblog_notify_siteadmin', 10, 2 );
 add_action( 'wpmu_activate_blog', 'wpmu_welcome_notification', 10, 5 );
-add_action( 'after_signup_site', 'wpmu_signup_blog_notification', 10, 7 );
+add_action( 'after_signup_site', 'wpmu_signup_blog_notification', 10, 8 );
 
 // Register Nonce
 add_action( 'signup_hidden_fields', 'signup_nonce_fields' );
Index: wp-includes/ms-functions.php
===================================================================
--- wp-includes/ms-functions.php	(revision 41829)
+++ wp-includes/ms-functions.php	(working copy)
@@ -704,10 +704,16 @@
  * @param array  $meta       Optional. Signup meta data. By default, contains the requested privacy setting and lang_id.
  */
 function wpmu_signup_blog( $domain, $path, $title, $user, $user_email, $meta = array() )  {
-	global $wpdb;
+	global $wpdb, $wp_hasher;
 
 	$key = substr( md5( time() . wp_rand() . $domain ), 0, 16 );
 
+	if ( empty( $wp_hasher ) ) {
+		$wp_hasher = new PasswordHash( 8, true );
+	}
+
+	$hashed = time() . ':' . $wp_hasher->HashPassword( $key );
+
 	/**
 	 * Filters the metadata for a site signup.
 	 *
@@ -722,8 +728,9 @@
 	 * @param string $user       The user's requested login name.
 	 * @param string $user_email The user's email address.
 	 * @param string $key        The user's activation key.
+	 * @param string $hashed     The user's hashed activation key.
 	 */
-	$meta = apply_filters( 'signup_site_meta', $meta, $domain, $path, $title, $user, $user_email, $key );
+	$meta = apply_filters( 'signup_site_meta', $meta, $domain, $path, $title, $user, $user_email, $key, $hashed );
 
 	$wpdb->insert( $wpdb->signups, array(
 		'domain' => $domain,
@@ -732,7 +739,7 @@
 		'user_login' => $user,
 		'user_email' => $user_email,
 		'registered' => current_time('mysql', true),
-		'activation_key' => $key,
+		'activation_key' => $hashed,
 		'meta' => serialize( $meta )
 	) );
 
@@ -748,8 +755,10 @@
 	 * @param string $user_email The user's email address.
 	 * @param string $key        The user's activation key.
 	 * @param array  $meta       Signup meta data. By default, contains the requested privacy setting and lang_id.
+	 * @param int    $signup_id  Signup ID.
+	 * @param string $hashed     The user's hashed activation key.
 	 */
-	do_action( 'after_signup_site', $domain, $path, $title, $user, $user_email, $key, $meta );
+	do_action( 'after_signup_site', $domain, $path, $title, $user, $user_email, $key, $meta, $wpdb->insert_id, $hashed );
 }
 
 /**
@@ -767,7 +776,7 @@
  * @param array  $meta       Optional. Signup meta data. Default empty array.
  */
 function wpmu_signup_user( $user, $user_email, $meta = array() ) {
-	global $wpdb;
+	global $wpdb, $wp_hasher;
 
 	// Format data
 	$user = preg_replace( '/\s+/', '', sanitize_user( $user, true ) );
@@ -774,6 +783,12 @@
 	$user_email = sanitize_email( $user_email );
 	$key = substr( md5( time() . wp_rand() . $user_email ), 0, 16 );
 
+	if ( empty( $wp_hasher ) ) {
+		$wp_hasher = new PasswordHash( 8, true );
+	}
+
+	$hashed = time() . ':' . $wp_hasher->HashPassword( $key );
+
 	/**
 	 * Filters the metadata for a user signup.
 	 *
@@ -785,8 +800,9 @@
 	 * @param string $user       The user's requested login name.
 	 * @param string $user_email The user's email address.
 	 * @param string $key        The user's activation key.
+	 * @param string $hashed     The user's hashed activation key.
 	 */
-	$meta = apply_filters( 'signup_user_meta', $meta, $user, $user_email, $key );
+	$meta = apply_filters( 'signup_user_meta', $meta, $user, $user_email, $key, $hashed );
 
 	$wpdb->insert( $wpdb->signups, array(
 		'domain' => '',
@@ -795,7 +811,7 @@
 		'user_login' => $user,
 		'user_email' => $user_email,
 		'registered' => current_time('mysql', true),
-		'activation_key' => $key,
+		'activation_key' => $hashed,
 		'meta' => serialize( $meta )
 	) );
 
@@ -808,8 +824,10 @@
 	 * @param string $user_email The user's email address.
 	 * @param string $key        The user's activation key.
 	 * @param array  $meta       Signup meta data. Default empty array.
+	 * @param int    $signup_id  Signup ID.
+	 * @param string $hashed     The user's hashed activation key.
 	 */
-	do_action( 'after_signup_user', $user, $user_email, $key, $meta );
+	do_action( 'after_signup_user', $user, $user_email, $key, $meta, $wpdb->insert_id, $hashed );
 }
 
 /**
@@ -835,9 +853,10 @@
  * @param string $user_email The user's email address.
  * @param string $key        The activation key created in wpmu_signup_blog()
  * @param array  $meta       Optional. Signup meta data. By default, contains the requested privacy setting and lang_id.
+ * @param int    $signup_id  Signup ID.
  * @return bool
  */
-function wpmu_signup_blog_notification( $domain, $path, $title, $user_login, $user_email, $key, $meta = array() ) {
+function wpmu_signup_blog_notification( $domain, $path, $title, $user_login, $user_email, $key, $meta = array(), $signup_id ) {
 	/**
 	 * Filters whether to bypass the new site email notification.
 	 *
@@ -857,9 +876,9 @@
 
 	// Send email with activation link.
 	if ( !is_subdomain_install() || get_current_network_id() != 1 )
-		$activate_url = network_site_url("wp-activate.php?key=$key");
+		$activate_url = network_site_url( "wp-activate.php?key=$key&signup_id=$signup_id" );
 	else
-		$activate_url = "http://{$domain}{$path}wp-activate.php?key=$key"; // @todo use *_url() API
+		$activate_url = "http://{$domain}{$path}wp-activate.php?key=$key&signup_id=$signup_id"; // @todo use *_url() API
 
 	$activate_url = esc_url($activate_url);
 	$admin_email = get_site_option( 'admin_email' );
@@ -949,9 +968,10 @@
  * @param string $user_email The user's email address.
  * @param string $key        The activation key created in wpmu_signup_user()
  * @param array  $meta       Optional. Signup meta data. Default empty array.
+ * @param int    $signup_id  Signup ID.
  * @return bool
  */
-function wpmu_signup_user_notification( $user_login, $user_email, $key, $meta = array() ) {
+function wpmu_signup_user_notification( $user_login, $user_email, $key, $meta = array(), $signup_id ) {
 	/**
 	 * Filters whether to bypass the email notification for new user sign-up.
 	 *
@@ -992,7 +1012,7 @@
 			__( "To activate your user, please click the following link:\n\n%s\n\nAfter you activate, you will receive *another email* with your login." ),
 			$user_login, $user_email, $key, $meta
 		),
-		site_url( "wp-activate.php?key=$key" )
+		site_url( "wp-activate.php?key=$key&signup_id=$signup_id" )
 	);
 	// TODO: Don't hard code activation link.
 	$subject = sprintf(
@@ -1037,16 +1057,50 @@
  * @global wpdb $wpdb WordPress database abstraction object.
  *
  * @param string $key The activation key provided to the user.
+ * @param int $signup_id The Signup ID.
  * @return array|WP_Error An array containing information about the activated user and/or blog
  */
-function wpmu_activate_signup($key) {
-	global $wpdb;
+function wpmu_activate_signup( $key, $signup_id ) {
+	global $wpdb, $wp_hasher;
 
-	$signup = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->signups WHERE activation_key = %s", $key) );
+	$signup = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->signups WHERE activation_key = %s OR signup_id = %d", $key, $signup_id ) );
 
-	if ( empty( $signup ) )
+	if ( empty( $signup ) ) {
+		return new WP_Error( 'invalid_id', __( 'Invalid signup ID.' ) );
+	}
+
+	if ( empty( $wp_hasher ) ) {
+		$wp_hasher = new PasswordHash( 8, true );
+	}
+
+	if ( $key === $signup->activation_key ) {
+		return new WP_Error( 'expired_key', __( 'Invalid key' ) );
+	}
+
+	if ( false !== strpos( $signup->activation_key, ':' ) ) {
 		return new WP_Error( 'invalid_key', __( 'Invalid activation key.' ) );
+	}
 
+	list( $pass_request_time, $signup_key ) = explode( ':', $signup->activation_key, 2 );
+
+	if ( ! $wp_hasher->CheckPassword( $key, $signup_key ) ) {
+		return new WP_Error( 'invalid_key', __( 'Invalid activation key.' ) );
+	}
+
+	/**
+	 * Filters the expiration time of password reset keys.
+	 *
+	 * @since 5.0
+	 *
+	 * @param int $expiration_duration The expiration time in seconds.
+	 */
+	$expiration_duration = apply_filters( 'activate_signup_expiration', DAY_IN_SECONDS );
+	$expiration_time     = $pass_request_time + $expiration_duration;
+
+	if ( time() < $expiration_time ) {
+		return new WP_Error( 'expired_key', __( 'Invalid key' ) );
+	}
+
 	if ( $signup->active ) {
 		if ( empty( $signup->domain ) )
 			return new WP_Error( 'already_active', __( 'The user is already active.' ), $signup );
