diff --git a/includes/common.inc b/includes/common.inc
index c97704c..803d211 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -5187,20 +5187,24 @@ function drupal_get_private_key() {
  * @param $value
  *   An additional value to base the token on.
  *
- * The generated token is based on the session ID of the current user. Normally,
+ * The generated token is based on the session of the current user. Normally,
  * anonymous users do not have a session, so the generated token will be
  * different on every page request. To generate a token for users without a
  * session, manually start a session prior to calling this function.
  *
  * @return string
- *   A 43-character URL-safe token for validation, based on the user session ID,
+ *   A 43-character URL-safe token for validation, based on the token seed,
  *   the hash salt provided from drupal_get_hash_salt(), and the
  *   'drupal_private_key' configuration variable.
  *
  * @see drupal_get_hash_salt()
  */
 function drupal_get_token($value = '') {
-  return drupal_hmac_base64($value, session_id() . drupal_get_private_key() . drupal_get_hash_salt());
+  if (empty($_SESSION['csrf_token_seed'])) {
+    $_SESSION['csrf_token_seed'] = drupal_random_key();
+  }
+
+  return drupal_compute_token($_SESSION['csrf_token_seed'], $value);
 }
 
 /**
@@ -5219,7 +5223,28 @@ function drupal_get_token($value = '') {
  */
 function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
   global $user;
-  return (($skip_anonymous && $user->uid == 0) || ($token === drupal_get_token($value)));
+  if (!$skip_anonymous && empty($_SESSION['csrf_token_seed'])) {
+    return FALSE;
+  }
+
+  return (($skip_anonymous && $user->uid == 0) || ($token === drupal_compute_token($_SESSION['csrf_token_seed'], $value)));
+}
+
+/**
+ * Generates a token_based on $value, the token seed, and the private key.
+ *
+ * @param string $seed
+ *   The per-session token seed.
+ * @param string $value
+ *   (optional) An additional value to base the token on.
+ *
+ * @return string
+ *   A 43-character URL-safe token for validation, based on the token seed,
+ *   the hash salt provided by drupal_get_hash_salt(), and the
+ *   'drupal_private_key' configuration variable.
+ */
+function drupal_compute_token($seed, $value = '') {
+  return drupal_hmac_base64($value, $seed . drupal_get_private_key() . drupal_get_hash_salt());
 }
 
 function _drupal_bootstrap_full() {
diff --git a/includes/session.inc b/includes/session.inc
index 25aa347..d45272f 100644
--- a/includes/session.inc
+++ b/includes/session.inc
@@ -249,7 +249,7 @@ function drupal_session_initialize() {
     // anonymous users not use a session cookie unless something is stored in
     // $_SESSION. This allows HTTP proxies to cache anonymous pageviews.
     drupal_session_start();
-    if (!empty($user->uid) || !empty($_SESSION)) {
+    if (!empty($user->uid) || !drupal_session_obsolete()) {
       drupal_page_is_cacheable(FALSE);
     }
   }
@@ -307,7 +307,7 @@ function drupal_session_commit() {
     return;
   }
 
-  if (empty($user->uid) && empty($_SESSION)) {
+  if (empty($user->uid) && drupal_session_obsolete()) {
     // There is no session data to store, destroy the session if it was
     // previously started.
     if (drupal_session_started()) {
@@ -374,6 +374,10 @@ function drupal_session_regenerate() {
   }
   session_id(drupal_random_key());
 
+  if (!empty($_SESSION)) {
+    unset($_SESSION['csrf_token_seed']);
+  }
+
   if (isset($old_session_id)) {
     $params = session_get_cookie_params();
     $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
@@ -533,3 +537,25 @@ function drupal_save_session($status = NULL) {
   }
   return $save_session;
 }
+
+/**
+ * Determines whether the session contains user data.
+ *
+ * @return bool
+ *   TRUE when the session does not contain any values and therefore can be
+ *   destroyed.
+ */
+function drupal_session_obsolete() {
+  // Return early when $_SESSION is empty or not initialized.
+  if (empty($_SESSION)) {
+    return TRUE;
+  }
+
+  // Ignore the CSRF token seed.
+  //
+  // Anonymous users should not get a CSRF token at any time, or if they do,
+  // then the originating code is responsible for cleaning up the session once
+  // obsolete. Since that is not guaranteed to be the case, this check force-
+  // ignores the CSRF token, so as to avoid performance regressions.
+  return count(array_diff_key($_SESSION, array('csrf_token_seed' => TRUE))) == 0;
+}
diff --git a/modules/simpletest/tests/session.test b/modules/simpletest/tests/session.test
index 893d03e..8699a1a 100644
--- a/modules/simpletest/tests/session.test
+++ b/modules/simpletest/tests/session.test
@@ -528,6 +528,71 @@ public function testEmptySessionId() {
   }
 
   /**
+   * Ensure that a CSRF form token is shared in SSL mixed mode.
+   */
+  protected function testCsrfTokenWithMixedModeSsl() {
+    global $is_https;
+
+    if ($is_https) {
+      $secure_session_name = session_name();
+      $insecure_session_name = substr(session_name(), 1);
+    }
+    else {
+      $secure_session_name = 'S' . session_name();
+      $insecure_session_name = session_name();
+    }
+
+    // Enable mixed mode SSL.
+    variable_set('https', TRUE);
+
+    $user = $this->drupalCreateUser(array('access administration pages'));
+
+    // Login using the HTTPS user-login form.
+    $this->drupalGet('user');
+    $form = $this->xpath('//form[@id="user-login-form"]');
+    $form[0]['action'] = $this->httpsUrl('user');
+    $edit = array('name' => $user->name, 'pass' => $user->pass_raw);
+    $this->drupalPost(NULL, $edit, t('Log in'));
+
+    // Collect session id cookies.
+    $sid = $this->cookies[$insecure_session_name]['value'];
+    $ssid = $this->cookies[$secure_session_name]['value'];
+    $this->assertSessionIds($sid, $ssid, 'Session has both secure and insecure SIDs');
+
+    // Retrieve the form via HTTP.
+    $this->curlClose();
+    $this->drupalGet($this->httpUrl('session-test/form'), array(), array('Cookie: ' . $insecure_session_name . '=' . $sid));
+    $http_token = $this->getFormToken();
+
+    // Verify that submitting form values via HTTPS to a form originally
+    // retrieved over HTTP works.
+    $form = $this->xpath('//form[@id="session-test-form"]');
+    $form[0]['action'] = $this->httpsUrl('session-test/form');
+    $edit = array('input' => $this->randomName(32));
+    $this->curlClose();
+    $this->drupalPost(NULL, $edit, 'Save', array('Cookie: ' . $secure_session_name . '=' . $ssid));
+    $this->assertText(format_string('Ok: @input', array('@input' => $edit['input'])));
+
+    // Retrieve the same form via HTTPS.
+    $this->curlClose();
+    $this->drupalGet($this->httpsUrl('session-test/form'), array(), array('Cookie: ' . $secure_session_name . '=' . $ssid));
+    $https_token = $this->getFormToken();
+
+    // Verify that CSRF token values are the same for a form regardless of
+    // whether it was accessed via HTTP or HTTPS when SSL mixed mode is enabled.
+    $this->assertEqual($http_token, $https_token, 'Form token is the same on HTTP as well as HTTPS form');
+  }
+
+  /**
+   * Return the token of the current form.
+   */
+  protected function getFormToken() {
+    $token_fields = $this->xpath('//input[@name="form_token"]');
+    $this->assertEqual(count($token_fields), 1, 'One form token field on the page');
+    return (string) $token_fields[0]['value'];
+  }
+
+  /**
    * Test that there exists a session with two specific session IDs.
    *
    * @param $sid
diff --git a/modules/simpletest/tests/session_test.module b/modules/simpletest/tests/session_test.module
index 689ff09..4663274 100644
--- a/modules/simpletest/tests/session_test.module
+++ b/modules/simpletest/tests/session_test.module
@@ -60,6 +60,13 @@ function session_test_menu() {
     'access callback' => 'user_is_logged_in',
     'type' => MENU_CALLBACK,
   );
+  $items['session-test/form'] = array(
+    'title' => 'Test form',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('session_test_form'),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
 
   return $items;
 }
@@ -190,3 +197,29 @@ function session_test_drupal_goto_alter(&$path, &$options, &$http_response_code)
 function _session_test_is_logged_in() {
   return t('User is logged in.');
 }
+
+/**
+ * Menu callback for the test config edit forms.
+ */
+function session_test_form($form, &$form_state) {
+  $form['input'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Input',
+    '#required' => TRUE,
+  );
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Save',
+  );
+
+  return $form;
+}
+
+/**
+ * Submit callback.
+ */
+function session_test_form_submit(&$form, &$form_state) {
+  drupal_set_message(format_string('Ok: @input', array('@input' => $form_state['values']['input'])));
+}
