diff --git a/token.test b/token.test
index 7c554fc..e362af3 100644
--- a/token.test
+++ b/token.test
@@ -12,7 +12,7 @@ class TokenTestHelper extends DrupalWebTestCase {
   }
 
   function assertToken($type, array $data, $token, $expected, array $options = array()) {
-    $this->assertTokens($type, $data, array($token => $expected), $options);
+    return $this->assertTokens($type, $data, array($token => $expected), $options);
   }
 
   function assertTokens($type, array $data, array $tokens, array $options = array()) {
@@ -25,10 +25,15 @@ class TokenTestHelper extends DrupalWebTestCase {
       elseif (!isset($values[$token])) {
         $this->fail(t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token)));
       }
+      elseif (!empty($options['regex'])) {
+        $this->assertTrue(preg_match('/^' . $expected . '$/', $values[$token]), t("Token value for [@type:@token] was '@actual', matching regular expression pattern '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $values[$token], '@expected' => $expected)));
+      }
       else {
         $this->assertIdentical($values[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $values[$token], '@expected' => $expected)));
       }
     }
+
+    return $values;
   }
 
   function assertNoTokens($type, array $data, array $tokens, array $options = array()) {
@@ -37,6 +42,7 @@ class TokenTestHelper extends DrupalWebTestCase {
     foreach ($tokens as $token) {
       $this->assertTrue(!isset($values[$token]), t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token)));
     }
+    return $values;
   }
 
   function saveAlias($source, $alias, $language = LANGUAGE_NONE) {
@@ -798,3 +804,29 @@ class TokenArrayTestCase extends TokenTestHelper {
     $this->assertTokens('array', array('array' => $array), $tokens);
   }
 }
+
+class TokenRandomTestCase extends TokenTestHelper {
+  public static function getInfo() {
+    return array(
+      'name' => 'Random token tests',
+      'description' => 'Test the random tokens.',
+      'group' => 'Token',
+    );
+  }
+
+  function testRandomTokens() {
+    $tokens = array(
+      'number' => '[0-9]{1,}',
+      'hash:md5' => '[0-9a-f]{32}',
+      'hash:sha1' => '[0-9a-f]{40}',
+      'hash:sha256' => '[0-9a-f]{64}',
+      'hash:invalid-algo' => NULL,
+    );
+
+    $first_set = $this->assertTokens('random', array(), $tokens, array('regex' => TRUE));
+    $second_set = $this->assertTokens('random', array(), $tokens, array('regex' => TRUE));
+    foreach ($first_set as $token => $value) {
+      $this->assertNotIdentical($first_set[$token], $second_set[$token]);
+    }
+  }
+}
diff --git a/token.tokens.inc b/token.tokens.inc
index 0c4951e..a5f352e 100644
--- a/token.tokens.inc
+++ b/token.tokens.inc
@@ -275,6 +275,21 @@ function token_token_info() {
     'dynamic' => TRUE,
   );
 
+  // Random tokens.
+  $info['types']['random'] = array(
+    'name' => t('Random'),
+    'description' => ('Tokens related to random data.'),
+  );
+  $info['tokens']['random']['number'] = array(
+    'name' => t('Number'),
+    'description' => t('A random number from 0 to @max.', array('@max' => mt_getrandmax())),
+  );
+  $info['tokens']['random']['hash'] = array(
+    'name' => t('Hash'),
+    'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', array('@hash-algos' => implode(', ', hash_algos()))),
+    'dynamic' => TRUE,
+  );
+
   return $info;
 }
 
@@ -665,6 +680,27 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
     // @todo Handle if the array values are not strings and could be chained.
   }
 
+  // Random tokens.
+  if ($type == 'random') {
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'number':
+          $replacements[$original] = mt_rand();
+          break;
+      }
+    }
+
+    // [custom:hash:*] dynamic token.
+    if ($hash_tokens = token_find_with_prefix($tokens, 'hash')) {
+      $algos = hash_algos();
+      foreach ($hash_tokens as $name => $original) {
+        if (in_array($name, $algos)) {
+          $replacements[$original] = hash($name, drupal_random_bytes(55));
+        }
+      }
+    }
+  }
+
   // If $type is a token type, $data[$type] is empty but $data[$entity_type] is
   // not, re-run token replacements.
   if (empty($data[$type]) && ($entity_type = token_get_entity_mapping('token', $type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
