Merge "Don't check namespace in SpecialWantedtemplates"
[lhc/web/wiklou.git] / includes / utils / MWCryptHKDF.php
1 <?php
2 /**
3 * Extract-and-Expand Key Derivation Function (HKDF). A cryptographicly
4 * secure key expansion function based on RFC 5869.
5 *
6 * This relies on the secrecy of $wgSecretKey (by default), or $wgHKDFSecret.
7 * By default, sha256 is used as the underlying hashing algorithm, but any other
8 * algorithm can be used. Finding the secret key from the output would require
9 * an attacker to discover the input key (the PRK) to the hmac that generated
10 * the output, and discover the particular data, hmac'ed with an evolving key
11 * (salt), to produce the PRK. Even with md5, no publicly known attacks make
12 * this currently feasible.
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 * http://www.gnu.org/copyleft/gpl.html
28 *
29 * @author Chris Steipp
30 * @file
31 */
32
33 class MWCryptHKDF {
34
35 /**
36 * Singleton instance for public use
37 */
38 protected static $singleton = null;
39
40 /**
41 * The persistant cache
42 */
43 protected $cache = null;
44
45 /**
46 * Cache key we'll use for our salt
47 */
48 protected $cacheKey = null;
49
50 /**
51 * The hash algorithm being used
52 */
53 protected $algorithm = null;
54
55 /**
56 * binary string, the salt for the HKDF
57 */
58 protected $salt;
59
60 /**
61 * The pseudorandom key
62 */
63 private $prk;
64
65 /**
66 * The secret key material. This must be kept secret to preserve
67 * the security properties of this RNG.
68 */
69 private $skm;
70
71 /**
72 * The last block (K(i)) of the most recent expanded key
73 */
74 protected $lastK;
75
76 /**
77 * a "context information" string CTXinfo (which may be null)
78 * See http://eprint.iacr.org/2010/264.pdf Section 4.1
79 */
80 protected $context = array();
81
82 /**
83 * Round count is computed based on the hash'es output length,
84 * which neither php nor openssl seem to provide easily.
85 */
86 public static $hashLength = array(
87 'md5' => 16,
88 'sha1' => 20,
89 'sha224' => 28,
90 'sha256' => 32,
91 'sha384' => 48,
92 'sha512' => 64,
93 'ripemd128' => 16,
94 'ripemd160' => 20,
95 'ripemd256' => 32,
96 'ripemd320' => 40,
97 'whirlpool' => 64,
98 );
99
100
101 /**
102 * @param string $secretKeyMaterial
103 * @param string $algorithm Name of hashing algorithm
104 * @param BagOStuff $cache
105 * @param string|array $context Context to mix into HKDF context
106 * @throws MWException
107 */
108 public function __construct( $secretKeyMaterial, $algorithm, $cache, $context ) {
109 if ( strlen( $secretKeyMaterial ) < 16 ) {
110 throw new MWException( "MWCryptHKDF secret was too short." );
111 }
112 $this->skm = $secretKeyMaterial;
113 $this->algorithm = $algorithm;
114 $this->cache = $cache;
115 $this->salt = ''; // Initialize a blank salt, see getSaltUsingCache()
116 $this->prk = '';
117 $this->context = is_array( $context ) ? $context : array( $context );
118
119 // To prevent every call from hitting the same memcache server, pick
120 // from a set of keys to use. mt_rand is only use to pick a random
121 // server, and does not affect the security of the process.
122 $this->cacheKey = wfMemcKey( 'HKDF', mt_rand( 0, 16 ) );
123 }
124
125 /**
126 * Save the last block generated, so the next user will compute a different PRK
127 * from the same SKM. This should keep things unpredictable even if an attacker
128 * is able to influence CTXinfo.
129 */
130 function __destruct() {
131 if ( $this->lastK ) {
132 $this->cache->set( $this->cacheKey, $this->lastK );
133 }
134 }
135
136 /**
137 * MW specific salt, cached from last run
138 * @return string Binary string
139 */
140 protected function getSaltUsingCache() {
141 if ( $this->salt == '' ) {
142 $lastSalt = $this->cache->get( $this->cacheKey );
143 if ( $lastSalt === false ) {
144 // If we don't have a previous value to use as our salt, we use
145 // 16 bytes from MWCryptRand, which will use a small amount of
146 // entropy from our pool. Note, "XTR may be deterministic or keyed
147 // via an optional “salt value” (i.e., a non-secret random
148 // value)..." - http://eprint.iacr.org/2010/264.pdf. However, we
149 // use a strongly random value since we can.
150 $lastSalt = MWCryptRand::generate( 16 );
151 }
152 // Get a binary string that is hashLen long
153 $this->salt = hash( $this->algorithm, $lastSalt, true );
154 }
155 return $this->salt;
156 }
157
158 /**
159 * Return a singleton instance, based on the global configs.
160 * @return HKDF
161 * @throws MWException
162 */
163 protected static function singleton() {
164 global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey, $wgMainCacheType;
165
166 $secret = $wgHKDFSecret ?: $wgSecretKey;
167 if ( !$secret ) {
168 throw new MWException( "Cannot use MWCryptHKDF without a secret." );
169 }
170
171 // In HKDF, the context can be known to the attacker, but this will
172 // keep simultaneous runs from producing the same output.
173 $context = array();
174 $context[] = microtime();
175 $context[] = getmypid();
176 $context[] = gethostname();
177
178 // Setup salt cache. Use APC, or fallback to the main cache if it isn't setup
179 $cache = ObjectCache::newAccelerator( $wgMainCacheType );
180
181 if ( is_null( self::$singleton ) ) {
182 self::$singleton = new self( $secret, $wgHKDFAlgorithm, $cache, $context );
183 }
184
185 return self::$singleton;
186 }
187
188 /**
189 * Produce $bytes of secure random data. As a side-effect,
190 * $this->lastK is set to the last hashLen block of key material.
191 * @param int $bytes Number of bytes of data
192 * @param string $context Context to mix into CTXinfo
193 * @return string Binary string of length $bytes
194 */
195 protected function realGenerate( $bytes, $context = '' ) {
196
197 if ( $this->prk === '' ) {
198 $salt = $this->getSaltUsingCache();
199 $this->prk = self::HKDFExtract(
200 $this->algorithm,
201 $salt,
202 $this->skm
203 );
204 }
205
206 $CTXinfo = implode( ':', array_merge( $this->context, array( $context ) ) );
207
208 return self::HKDFExpand(
209 $this->algorithm,
210 $this->prk,
211 $CTXinfo,
212 $bytes,
213 $this->lastK
214 );
215 }
216
217
218 /**
219 * RFC5869 defines HKDF in 2 steps, extraction and expansion.
220 * From http://eprint.iacr.org/2010/264.pdf:
221 *
222 * The scheme HKDF is specifed as:
223 * HKDF(XTS, SKM, CTXinfo, L) = K(1) || K(2) || ... || K(t)
224 * where the values K(i) are defined as follows:
225 * PRK = HMAC(XTS, SKM)
226 * K(1) = HMAC(PRK, CTXinfo || 0);
227 * K(i+1) = HMAC(PRK, K(i) || CTXinfo || i), 1 <= i < t;
228 * where t = [L/k] and the value K(t) is truncated to its first d = L mod k bits;
229 * the counter i is non-wrapping and of a given fixed size, e.g., a single byte.
230 * Note that the length of the HMAC output is the same as its key length and therefore
231 * the scheme is well defined.
232 *
233 * XTS is the "extractor salt"
234 * SKM is the "secret keying material"
235 *
236 * N.B. http://eprint.iacr.org/2010/264.pdf seems to differ from RFC 5869 in that the test
237 * vectors from RFC 5869 only work if K(0) = '' and K(1) = HMAC(PRK, K(0) || CTXinfo || 1)
238 *
239 * @param string $hash The hashing function to use (e.g., sha256)
240 * @param string $ikm The input keying material
241 * @param string $salt The salt to add to the ikm, to get the prk
242 * @param string $info Optional context (change the output without affecting
243 * the randomness properties of the output)
244 * @param int $L Number of bytes to return
245 * @return string Cryptographically secure pseudorandom binary string
246 */
247 public static function HKDF( $hash, $ikm, $salt, $info, $L ) {
248 $prk = self::HKDFExtract( $hash, $salt, $ikm );
249 $okm = self::HKDFExpand( $hash, $prk, $info, $L );
250 return $okm;
251 }
252
253 /**
254 * Extract the PRK, PRK = HMAC(XTS, SKM)
255 * Note that the hmac is keyed with XTS (the salt),
256 * and the SKM (source key material) is the "data".
257 *
258 * @param string $hash The hashing function to use (e.g., sha256)
259 * @param string $salt The salt to add to the ikm, to get the prk
260 * @param string $ikm The input keying material
261 * @return string Binary string (pseudorandm key) used as input to HKDFExpand
262 */
263 private static function HKDFExtract( $hash, $salt, $ikm ) {
264 return hash_hmac( $hash, $ikm, $salt, true );
265 }
266
267 /**
268 * Expand the key with the given context
269 *
270 * @param string $hash Hashing Algorithm
271 * @param string $prk A pseudorandom key of at least HashLen octets
272 * (usually, the output from the extract step)
273 * @param string $info Optional context and application specific information
274 * (can be a zero-length string)
275 * @param int $bytes Length of output keying material in bytes
276 * (<= 255*HashLen)
277 * @param string &$lastK Set by this function to the last block of the expansion.
278 * In MediaWiki, this is used to seed future Extractions.
279 * @return string Cryptographically secure random string $bytes long
280 * @throws MWException
281 */
282 private static function HKDFExpand( $hash, $prk, $info, $bytes, &$lastK = '' ) {
283 $hashLen = MWCryptHKDF::$hashLength[$hash];
284 $rounds = ceil( $bytes / $hashLen );
285 $output = '';
286
287 if ( $bytes > 255 * $hashLen ) {
288 throw new MWException( "Too many bytes requested from HDKFExpand" );
289 }
290
291 // K(1) = HMAC(PRK, CTXinfo || 1);
292 // K(i) = HMAC(PRK, K(i-1) || CTXinfo || i); 1 < i <= t;
293 for ( $counter = 1; $counter <= $rounds; ++$counter ) {
294 $lastK = hash_hmac(
295 $hash,
296 $lastK . $info . chr( $counter ),
297 $prk,
298 true
299 );
300 $output .= $lastK;
301 }
302
303 return substr( $output, 0, $bytes );
304 }
305
306 /**
307 * Generate cryptographically random data and return it in raw binary form.
308 *
309 * @param int $bytes The number of bytes of random data to generate
310 * @param string $context String to mix into HMAC context
311 * @return string Binary string of length $bytes
312 */
313 public static function generate( $bytes, $context ) {
314 return self::singleton()->realGenerate( $bytes, $context );
315 }
316
317 /**
318 * Generate cryptographically random data and return it in hexadecimal string format.
319 * See MWCryptRand::realGenerateHex for details of the char-to-byte conversion logic.
320 *
321 * @param int $chars The number of hex chars of random data to generate
322 * @param string $context String to mix into HMAC context
323 * @return string Random hex characters, $chars long
324 */
325 public static function generateHex( $chars, $context = '' ) {
326 $bytes = ceil( $chars / 2 );
327 $hex = bin2hex( self::singleton()->realGenerate( $bytes, $context ) );
328 return substr( $hex, 0, $chars );
329 }
330
331 }