PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/utils/MWCryptHKDF.php

https://gitlab.com/link233/bootmw
PHP | 329 lines | 120 code | 34 blank | 175 comment | 10 complexity | 32c5de47000d9fffec1bf93c8e96c0c0 MD5 | raw file
  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. class MWCryptHKDF {
  33. /**
  34. * Singleton instance for public use
  35. */
  36. protected static $singleton = null;
  37. /**
  38. * The persistant cache
  39. */
  40. protected $cache = null;
  41. /**
  42. * Cache key we'll use for our salt
  43. */
  44. protected $cacheKey = null;
  45. /**
  46. * The hash algorithm being used
  47. */
  48. protected $algorithm = null;
  49. /**
  50. * binary string, the salt for the HKDF
  51. */
  52. protected $salt;
  53. /**
  54. * The pseudorandom key
  55. */
  56. private $prk;
  57. /**
  58. * The secret key material. This must be kept secret to preserve
  59. * the security properties of this RNG.
  60. */
  61. private $skm;
  62. /**
  63. * The last block (K(i)) of the most recent expanded key
  64. */
  65. protected $lastK;
  66. /**
  67. * a "context information" string CTXinfo (which may be null)
  68. * See http://eprint.iacr.org/2010/264.pdf Section 4.1
  69. */
  70. protected $context = [];
  71. /**
  72. * Round count is computed based on the hash'es output length,
  73. * which neither php nor openssl seem to provide easily.
  74. */
  75. public static $hashLength = [
  76. 'md5' => 16,
  77. 'sha1' => 20,
  78. 'sha224' => 28,
  79. 'sha256' => 32,
  80. 'sha384' => 48,
  81. 'sha512' => 64,
  82. 'ripemd128' => 16,
  83. 'ripemd160' => 20,
  84. 'ripemd256' => 32,
  85. 'ripemd320' => 40,
  86. 'whirlpool' => 64,
  87. ];
  88. /**
  89. * @param string $secretKeyMaterial
  90. * @param string $algorithm Name of hashing algorithm
  91. * @param BagOStuff $cache
  92. * @param string|array $context Context to mix into HKDF context
  93. * @throws MWException
  94. */
  95. public function __construct( $secretKeyMaterial, $algorithm, $cache, $context ) {
  96. if ( strlen( $secretKeyMaterial ) < 16 ) {
  97. throw new MWException( "MWCryptHKDF secret was too short." );
  98. }
  99. $this->skm = $secretKeyMaterial;
  100. $this->algorithm = $algorithm;
  101. $this->cache = $cache;
  102. $this->salt = ''; // Initialize a blank salt, see getSaltUsingCache()
  103. $this->prk = '';
  104. $this->context = is_array( $context ) ? $context : [ $context ];
  105. // To prevent every call from hitting the same memcache server, pick
  106. // from a set of keys to use. mt_rand is only use to pick a random
  107. // server, and does not affect the security of the process.
  108. $this->cacheKey = wfMemcKey( 'HKDF', mt_rand( 0, 16 ) );
  109. }
  110. /**
  111. * Save the last block generated, so the next user will compute a different PRK
  112. * from the same SKM. This should keep things unpredictable even if an attacker
  113. * is able to influence CTXinfo.
  114. */
  115. function __destruct() {
  116. if ( $this->lastK ) {
  117. $this->cache->set( $this->cacheKey, $this->lastK );
  118. }
  119. }
  120. /**
  121. * MW specific salt, cached from last run
  122. * @return string Binary string
  123. */
  124. protected function getSaltUsingCache() {
  125. if ( $this->salt == '' ) {
  126. $lastSalt = $this->cache->get( $this->cacheKey );
  127. if ( $lastSalt === false ) {
  128. // If we don't have a previous value to use as our salt, we use
  129. // 16 bytes from MWCryptRand, which will use a small amount of
  130. // entropy from our pool. Note, "XTR may be deterministic or keyed
  131. // via an optional “salt value” (i.e., a non-secret random
  132. // value)..." - http://eprint.iacr.org/2010/264.pdf. However, we
  133. // use a strongly random value since we can.
  134. $lastSalt = MWCryptRand::generate( 16 );
  135. }
  136. // Get a binary string that is hashLen long
  137. $this->salt = hash( $this->algorithm, $lastSalt, true );
  138. }
  139. return $this->salt;
  140. }
  141. /**
  142. * Return a singleton instance, based on the global configs.
  143. * @return HKDF
  144. * @throws MWException
  145. */
  146. protected static function singleton() {
  147. global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey, $wgMainCacheType;
  148. $secret = $wgHKDFSecret ?: $wgSecretKey;
  149. if ( !$secret ) {
  150. throw new MWException( "Cannot use MWCryptHKDF without a secret." );
  151. }
  152. // In HKDF, the context can be known to the attacker, but this will
  153. // keep simultaneous runs from producing the same output.
  154. $context = [];
  155. $context[] = microtime();
  156. $context[] = getmypid();
  157. $context[] = gethostname();
  158. // Setup salt cache. Use APC, or fallback to the main cache if it isn't setup
  159. $cache = ObjectCache::getLocalServerInstance( $wgMainCacheType );
  160. if ( is_null( self::$singleton ) ) {
  161. self::$singleton = new self( $secret, $wgHKDFAlgorithm, $cache, $context );
  162. }
  163. return self::$singleton;
  164. }
  165. /**
  166. * Produce $bytes of secure random data. As a side-effect,
  167. * $this->lastK is set to the last hashLen block of key material.
  168. * @param int $bytes Number of bytes of data
  169. * @param string $context Context to mix into CTXinfo
  170. * @return string Binary string of length $bytes
  171. */
  172. protected function realGenerate( $bytes, $context = '' ) {
  173. if ( $this->prk === '' ) {
  174. $salt = $this->getSaltUsingCache();
  175. $this->prk = self::HKDFExtract(
  176. $this->algorithm,
  177. $salt,
  178. $this->skm
  179. );
  180. }
  181. $CTXinfo = implode( ':', array_merge( $this->context, [ $context ] ) );
  182. return self::HKDFExpand(
  183. $this->algorithm,
  184. $this->prk,
  185. $CTXinfo,
  186. $bytes,
  187. $this->lastK
  188. );
  189. }
  190. /**
  191. * RFC5869 defines HKDF in 2 steps, extraction and expansion.
  192. * From http://eprint.iacr.org/2010/264.pdf:
  193. *
  194. * The scheme HKDF is specifed as:
  195. * HKDF(XTS, SKM, CTXinfo, L) = K(1) || K(2) || ... || K(t)
  196. * where the values K(i) are defined as follows:
  197. * PRK = HMAC(XTS, SKM)
  198. * K(1) = HMAC(PRK, CTXinfo || 0);
  199. * K(i+1) = HMAC(PRK, K(i) || CTXinfo || i), 1 <= i < t;
  200. * where t = [L/k] and the value K(t) is truncated to its first d = L mod k bits;
  201. * the counter i is non-wrapping and of a given fixed size, e.g., a single byte.
  202. * Note that the length of the HMAC output is the same as its key length and therefore
  203. * the scheme is well defined.
  204. *
  205. * XTS is the "extractor salt"
  206. * SKM is the "secret keying material"
  207. *
  208. * N.B. http://eprint.iacr.org/2010/264.pdf seems to differ from RFC 5869 in that the test
  209. * vectors from RFC 5869 only work if K(0) = '' and K(1) = HMAC(PRK, K(0) || CTXinfo || 1)
  210. *
  211. * @param string $hash The hashing function to use (e.g., sha256)
  212. * @param string $ikm The input keying material
  213. * @param string $salt The salt to add to the ikm, to get the prk
  214. * @param string $info Optional context (change the output without affecting
  215. * the randomness properties of the output)
  216. * @param int $L Number of bytes to return
  217. * @return string Cryptographically secure pseudorandom binary string
  218. */
  219. public static function HKDF( $hash, $ikm, $salt, $info, $L ) {
  220. $prk = self::HKDFExtract( $hash, $salt, $ikm );
  221. $okm = self::HKDFExpand( $hash, $prk, $info, $L );
  222. return $okm;
  223. }
  224. /**
  225. * Extract the PRK, PRK = HMAC(XTS, SKM)
  226. * Note that the hmac is keyed with XTS (the salt),
  227. * and the SKM (source key material) is the "data".
  228. *
  229. * @param string $hash The hashing function to use (e.g., sha256)
  230. * @param string $salt The salt to add to the ikm, to get the prk
  231. * @param string $ikm The input keying material
  232. * @return string Binary string (pseudorandm key) used as input to HKDFExpand
  233. */
  234. private static function HKDFExtract( $hash, $salt, $ikm ) {
  235. return hash_hmac( $hash, $ikm, $salt, true );
  236. }
  237. /**
  238. * Expand the key with the given context
  239. *
  240. * @param string $hash Hashing Algorithm
  241. * @param string $prk A pseudorandom key of at least HashLen octets
  242. * (usually, the output from the extract step)
  243. * @param string $info Optional context and application specific information
  244. * (can be a zero-length string)
  245. * @param int $bytes Length of output keying material in bytes
  246. * (<= 255*HashLen)
  247. * @param string &$lastK Set by this function to the last block of the expansion.
  248. * In MediaWiki, this is used to seed future Extractions.
  249. * @return string Cryptographically secure random string $bytes long
  250. * @throws MWException
  251. */
  252. private static function HKDFExpand( $hash, $prk, $info, $bytes, &$lastK = '' ) {
  253. $hashLen = MWCryptHKDF::$hashLength[$hash];
  254. $rounds = ceil( $bytes / $hashLen );
  255. $output = '';
  256. if ( $bytes > 255 * $hashLen ) {
  257. throw new MWException( "Too many bytes requested from HDKFExpand" );
  258. }
  259. // K(1) = HMAC(PRK, CTXinfo || 1);
  260. // K(i) = HMAC(PRK, K(i-1) || CTXinfo || i); 1 < i <= t;
  261. for ( $counter = 1; $counter <= $rounds; ++$counter ) {
  262. $lastK = hash_hmac(
  263. $hash,
  264. $lastK . $info . chr( $counter ),
  265. $prk,
  266. true
  267. );
  268. $output .= $lastK;
  269. }
  270. return substr( $output, 0, $bytes );
  271. }
  272. /**
  273. * Generate cryptographically random data and return it in raw binary form.
  274. *
  275. * @param int $bytes The number of bytes of random data to generate
  276. * @param string $context String to mix into HMAC context
  277. * @return string Binary string of length $bytes
  278. */
  279. public static function generate( $bytes, $context ) {
  280. return self::singleton()->realGenerate( $bytes, $context );
  281. }
  282. /**
  283. * Generate cryptographically random data and return it in hexadecimal string format.
  284. * See MWCryptRand::realGenerateHex for details of the char-to-byte conversion logic.
  285. *
  286. * @param int $chars The number of hex chars of random data to generate
  287. * @param string $context String to mix into HMAC context
  288. * @return string Random hex characters, $chars long
  289. */
  290. public static function generateHex( $chars, $context = '' ) {
  291. $bytes = ceil( $chars / 2 );
  292. $hex = bin2hex( self::singleton()->realGenerate( $bytes, $context ) );
  293. return substr( $hex, 0, $chars );
  294. }
  295. }