aboutsummaryrefslogtreecommitdiff
path: root/engine/classes/ElggCrypto.php
blob: 364af4542487b6f98379f70dfa78614b13ec49fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php
/**
 * ElggCrypto
 *
 * @package    Elgg.Core
 * @subpackage Crypto
 *
 * @access private
 */
class ElggCrypto {

	/**
	 * Character set for temp passwords (no risk of embedded profanity/glyphs that look similar)
	 */
	const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789';

	/**
	 * Returns a string of highly randomized bytes (over the full 8-bit range).
	 *
	 * This function is better than simply calling mt_rand() or any other built-in
	 * PHP function because it can return a long string of bytes (compared to < 4
	 * bytes normally from mt_rand()) and uses the best available pseudo-random
	 * source.
	 *
	 * @param int $count The number of characters (bytes) to return in the string.
	 * @return string
	 *
	 * @copyright Copyright 2001 - 2012 by the original authors
	 *            https://github.com/drupal/drupal/blob/7.x/COPYRIGHT.txt
	 * @license   https://github.com/drupal/drupal/blob/7.x/LICENSE.txt GPL 2
	 *
	 * @see https://github.com/drupal/drupal/blob/7.x/includes/bootstrap.inc#L1942
	 */
	public static function getRandomBytes($count)  {
		// $random_state does not use drupal_static as it stores random bytes.
		static $random_state, $bytes, $php_compatible;
		// Initialize on the first call. The contents of $_SERVER includes a mix of
		// user-specific and system information that varies a little with each page.
		if (!isset($random_state)) {
			$random_state = print_r($_SERVER, true);
			if (function_exists('getmypid')) {
				// Further initialize with the somewhat random PHP process ID.
				$random_state .= getmypid();
			}
			$bytes = '';
		}
		if (strlen($bytes) < $count) {
			// PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
			// locking on Windows and rendered it unusable.
			if (!isset($php_compatible)) {
				$php_compatible = version_compare(PHP_VERSION, '5.3.4', '>=');
			}
			// /dev/urandom is available on many *nix systems and is considered the
			// best commonly available pseudo-random source.
			if ($fh = @fopen('/dev/urandom', 'rb')) {
				// PHP only performs buffered reads, so in reality it will always read
				// at least 4096 bytes. Thus, it costs nothing extra to read and store
				// that much so as to speed any additional invocations.
				$bytes .= fread($fh, max(4096, $count));
				fclose($fh);
			} elseif ($php_compatible && function_exists('openssl_random_pseudo_bytes')) {
				// openssl_random_pseudo_bytes() will find entropy in a system-dependent
				// way.
				$bytes .= openssl_random_pseudo_bytes($count - strlen($bytes));
			}
			// If /dev/urandom is not available or returns no bytes, this loop will
			// generate a good set of pseudo-random bytes on any system.
			// Note that it may be important that our $random_state is passed
			// through hash() prior to being rolled into $output, that the two hash()
			// invocations are different, and that the extra input into the first one -
			// the microtime() - is prepended rather than appended. This is to avoid
			// directly leaking $random_state via the $output stream, which could
			// allow for trivial prediction of further "random" numbers.
			while (strlen($bytes) < $count) {
				$random_state = hash('sha256', microtime() . mt_rand() . $random_state);
				$bytes .= hash('sha256', mt_rand() . $random_state, true);
			}
		}
		$output = substr($bytes, 0, $count);
		$bytes = substr($bytes, $count);
		return $output;
	}

	/**
	 * Generate a random string of specified length.
	 *
	 * Uses supplied character list for generating the new string.
	 * If no character list provided - uses Base64 URL character set.
	 *
	 * @param  int         $length Desired length of the string
	 * @param  string|null $chars  Characters to be chosen from randomly. If not given, the Base64 URL
	 *                             charset will be used.
	 *
	 * @return string The random string
	 *
	 * @throws InvalidArgumentException
	 *
	 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
	 * @license   http://framework.zend.com/license/new-bsd New BSD License
	 *
	 * @see https://github.com/zendframework/zf2/blob/master/library/Zend/Math/Rand.php#L179
	 */
	public static function getRandomString($length, $chars = null)
	{
		if ($length < 1) {
			throw new InvalidArgumentException('Length should be >= 1');
		}

		if (empty($chars)) {
			$numBytes = ceil($length * 0.75);
			$bytes    = self::getRandomBytes($numBytes);
			$string = substr(rtrim(base64_encode($bytes), '='), 0, $length);

			// Base64 URL
			return strtr($string, '+/', '-_');
		}

		$listLen = strlen($chars);

		if ($listLen == 1) {
			return str_repeat($chars, $length);
		}

		$bytes  = self::getRandomBytes($length);
		$pos    = 0;
		$result = '';
		for ($i = 0; $i < $length; $i++) {
			$pos     = ($pos + ord($bytes[$i])) % $listLen;
			$result .= $chars[$pos];
		}

		return $result;
	}
}