annotate core/lib/Drupal/Component/Utility/Crypt.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 4c8ae668cc8c
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Component\Utility;
Chris@0 4
Chris@0 5 /**
Chris@0 6 * Utility class for cryptographically-secure string handling routines.
Chris@0 7 *
Chris@0 8 * @ingroup utility
Chris@0 9 */
Chris@0 10 class Crypt {
Chris@0 11
Chris@0 12 /**
Chris@0 13 * Returns a string of highly randomized bytes (over the full 8-bit range).
Chris@0 14 *
Chris@0 15 * This function is better than simply calling mt_rand() or any other built-in
Chris@0 16 * PHP function because it can return a long string of bytes (compared to < 4
Chris@0 17 * bytes normally from mt_rand()) and uses the best available pseudo-random
Chris@0 18 * source.
Chris@0 19 *
Chris@0 20 * In PHP 7 and up, this uses the built-in PHP function random_bytes().
Chris@0 21 * In older PHP versions, this uses the random_bytes() function provided by
Chris@0 22 * the random_compat library, or the fallback hash-based generator from Drupal
Chris@0 23 * 7.x.
Chris@0 24 *
Chris@0 25 * @param int $count
Chris@0 26 * The number of characters (bytes) to return in the string.
Chris@0 27 *
Chris@0 28 * @return string
Chris@0 29 * A randomly generated string.
Chris@0 30 */
Chris@0 31 public static function randomBytes($count) {
Chris@0 32 try {
Chris@0 33 return random_bytes($count);
Chris@0 34 }
Chris@0 35 catch (\Exception $e) {
Chris@0 36 // $random_state does not use drupal_static as it stores random bytes.
Chris@0 37 static $random_state, $bytes;
Chris@0 38 // If the compatibility library fails, this simple hash-based PRNG will
Chris@0 39 // generate a good set of pseudo-random bytes on any system.
Chris@0 40 // Note that it may be important that our $random_state is passed
Chris@0 41 // through hash() prior to being rolled into $output, that the two hash()
Chris@0 42 // invocations are different, and that the extra input into the first one
Chris@0 43 // - the microtime() - is prepended rather than appended. This is to avoid
Chris@0 44 // directly leaking $random_state via the $output stream, which could
Chris@0 45 // allow for trivial prediction of further "random" numbers.
Chris@0 46 if (strlen($bytes) < $count) {
Chris@0 47 // Initialize on the first call. The $_SERVER variable includes user and
Chris@0 48 // system-specific information that varies a little with each page.
Chris@0 49 if (!isset($random_state)) {
Chris@0 50 $random_state = print_r($_SERVER, TRUE);
Chris@0 51 if (function_exists('getmypid')) {
Chris@0 52 // Further initialize with the somewhat random PHP process ID.
Chris@0 53 $random_state .= getmypid();
Chris@0 54 }
Chris@0 55 $bytes = '';
Chris@0 56 // Ensure mt_rand() is reseeded before calling it the first time.
Chris@0 57 mt_srand();
Chris@0 58 }
Chris@0 59
Chris@0 60 do {
Chris@0 61 $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
Chris@0 62 $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
Chris@0 63 } while (strlen($bytes) < $count);
Chris@0 64 }
Chris@0 65 $output = substr($bytes, 0, $count);
Chris@0 66 $bytes = substr($bytes, $count);
Chris@0 67 return $output;
Chris@0 68 }
Chris@0 69 }
Chris@0 70
Chris@0 71 /**
Chris@0 72 * Calculates a base-64 encoded, URL-safe sha-256 hmac.
Chris@0 73 *
Chris@0 74 * @param mixed $data
Chris@0 75 * Scalar value to be validated with the hmac.
Chris@0 76 * @param mixed $key
Chris@0 77 * A secret key, this can be any scalar value.
Chris@0 78 *
Chris@0 79 * @return string
Chris@0 80 * A base-64 encoded sha-256 hmac, with + replaced with -, / with _ and
Chris@0 81 * any = padding characters removed.
Chris@0 82 */
Chris@0 83 public static function hmacBase64($data, $key) {
Chris@0 84 // $data and $key being strings here is necessary to avoid empty string
Chris@0 85 // results of the hash function if they are not scalar values. As this
Chris@0 86 // function is used in security-critical contexts like token validation it
Chris@0 87 // is important that it never returns an empty string.
Chris@0 88 if (!is_scalar($data) || !is_scalar($key)) {
Chris@0 89 throw new \InvalidArgumentException('Both parameters passed to \Drupal\Component\Utility\Crypt::hmacBase64 must be scalar values.');
Chris@0 90 }
Chris@0 91
Chris@0 92 $hmac = base64_encode(hash_hmac('sha256', $data, $key, TRUE));
Chris@0 93 // Modify the hmac so it's safe to use in URLs.
Chris@0 94 return str_replace(['+', '/', '='], ['-', '_', ''], $hmac);
Chris@0 95 }
Chris@0 96
Chris@0 97 /**
Chris@0 98 * Calculates a base-64 encoded, URL-safe sha-256 hash.
Chris@0 99 *
Chris@0 100 * @param string $data
Chris@0 101 * String to be hashed.
Chris@0 102 *
Chris@0 103 * @return string
Chris@0 104 * A base-64 encoded sha-256 hash, with + replaced with -, / with _ and
Chris@0 105 * any = padding characters removed.
Chris@0 106 */
Chris@0 107 public static function hashBase64($data) {
Chris@0 108 $hash = base64_encode(hash('sha256', $data, TRUE));
Chris@0 109 // Modify the hash so it's safe to use in URLs.
Chris@0 110 return str_replace(['+', '/', '='], ['-', '_', ''], $hash);
Chris@0 111 }
Chris@0 112
Chris@0 113 /**
Chris@0 114 * Compares strings in constant time.
Chris@0 115 *
Chris@0 116 * @param string $known_string
Chris@0 117 * The expected string.
Chris@0 118 * @param string $user_string
Chris@0 119 * The user supplied string to check.
Chris@0 120 *
Chris@0 121 * @return bool
Chris@0 122 * Returns TRUE when the two strings are equal, FALSE otherwise.
Chris@0 123 */
Chris@0 124 public static function hashEquals($known_string, $user_string) {
Chris@0 125 if (function_exists('hash_equals')) {
Chris@0 126 return hash_equals($known_string, $user_string);
Chris@0 127 }
Chris@0 128 else {
Chris@0 129 // Backport of hash_equals() function from PHP 5.6
Chris@0 130 // @see https://github.com/php/php-src/blob/PHP-5.6/ext/hash/hash.c#L739
Chris@0 131 if (!is_string($known_string)) {
Chris@0 132 trigger_error(sprintf("Expected known_string to be a string, %s given", gettype($known_string)), E_USER_WARNING);
Chris@0 133 return FALSE;
Chris@0 134 }
Chris@0 135
Chris@0 136 if (!is_string($user_string)) {
Chris@0 137 trigger_error(sprintf("Expected user_string to be a string, %s given", gettype($user_string)), E_USER_WARNING);
Chris@0 138 return FALSE;
Chris@0 139 }
Chris@0 140
Chris@0 141 $known_len = strlen($known_string);
Chris@0 142 if ($known_len !== strlen($user_string)) {
Chris@0 143 return FALSE;
Chris@0 144 }
Chris@0 145
Chris@0 146 // This is security sensitive code. Do not optimize this for speed.
Chris@0 147 $result = 0;
Chris@0 148 for ($i = 0; $i < $known_len; $i++) {
Chris@0 149 $result |= (ord($known_string[$i]) ^ ord($user_string[$i]));
Chris@0 150 }
Chris@0 151
Chris@0 152 return $result === 0;
Chris@0 153 }
Chris@0 154 }
Chris@0 155
Chris@0 156 /**
Chris@0 157 * Returns a URL-safe, base64 encoded string of highly randomized bytes.
Chris@0 158 *
Chris@0 159 * @param $count
Chris@0 160 * The number of random bytes to fetch and base64 encode.
Chris@0 161 *
Chris@0 162 * @return string
Chris@0 163 * The base64 encoded result will have a length of up to 4 * $count.
Chris@0 164 *
Chris@0 165 * @see \Drupal\Component\Utility\Crypt::randomBytes()
Chris@0 166 */
Chris@0 167 public static function randomBytesBase64($count = 32) {
Chris@0 168 return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode(static::randomBytes($count)));
Chris@0 169 }
Chris@0 170
Chris@0 171 }