MediaWiki  1.27.2
MWCryptHKDF.php
Go to the documentation of this file.
1 <?php
33 class MWCryptHKDF {
34 
38  protected static $singleton = null;
39 
43  protected $cache = null;
44 
48  protected $cacheKey = null;
49 
53  protected $algorithm = null;
54 
58  protected $salt;
59 
63  private $prk;
64 
69  private $skm;
70 
74  protected $lastK;
75 
80  protected $context = [];
81 
86  public static $hashLength = [
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 
107  public function __construct( $secretKeyMaterial, $algorithm, $cache, $context ) {
108  if ( strlen( $secretKeyMaterial ) < 16 ) {
109  throw new MWException( "MWCryptHKDF secret was too short." );
110  }
111  $this->skm = $secretKeyMaterial;
112  $this->algorithm = $algorithm;
113  $this->cache = $cache;
114  $this->salt = ''; // Initialize a blank salt, see getSaltUsingCache()
115  $this->prk = '';
116  $this->context = is_array( $context ) ? $context : [ $context ];
117 
118  // To prevent every call from hitting the same memcache server, pick
119  // from a set of keys to use. mt_rand is only use to pick a random
120  // server, and does not affect the security of the process.
121  $this->cacheKey = wfMemcKey( 'HKDF', mt_rand( 0, 16 ) );
122  }
123 
129  function __destruct() {
130  if ( $this->lastK ) {
131  $this->cache->set( $this->cacheKey, $this->lastK );
132  }
133  }
134 
139  protected function getSaltUsingCache() {
140  if ( $this->salt == '' ) {
141  $lastSalt = $this->cache->get( $this->cacheKey );
142  if ( $lastSalt === false ) {
143  // If we don't have a previous value to use as our salt, we use
144  // 16 bytes from MWCryptRand, which will use a small amount of
145  // entropy from our pool. Note, "XTR may be deterministic or keyed
146  // via an optional “salt value” (i.e., a non-secret random
147  // value)..." - http://eprint.iacr.org/2010/264.pdf. However, we
148  // use a strongly random value since we can.
149  $lastSalt = MWCryptRand::generate( 16 );
150  }
151  // Get a binary string that is hashLen long
152  $this->salt = hash( $this->algorithm, $lastSalt, true );
153  }
154  return $this->salt;
155  }
156 
162  protected static function singleton() {
163  global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey, $wgMainCacheType;
164 
165  $secret = $wgHKDFSecret ?: $wgSecretKey;
166  if ( !$secret ) {
167  throw new MWException( "Cannot use MWCryptHKDF without a secret." );
168  }
169 
170  // In HKDF, the context can be known to the attacker, but this will
171  // keep simultaneous runs from producing the same output.
172  $context = [];
173  $context[] = microtime();
174  $context[] = getmypid();
175  $context[] = gethostname();
176 
177  // Setup salt cache. Use APC, or fallback to the main cache if it isn't setup
178  $cache = ObjectCache::getLocalServerInstance( $wgMainCacheType );
179 
180  if ( is_null( self::$singleton ) ) {
181  self::$singleton = new self( $secret, $wgHKDFAlgorithm, $cache, $context );
182  }
183 
184  return self::$singleton;
185  }
186 
194  protected function realGenerate( $bytes, $context = '' ) {
195 
196  if ( $this->prk === '' ) {
197  $salt = $this->getSaltUsingCache();
198  $this->prk = self::HKDFExtract(
199  $this->algorithm,
200  $salt,
201  $this->skm
202  );
203  }
204 
205  $CTXinfo = implode( ':', array_merge( $this->context, [ $context ] ) );
206 
207  return self::HKDFExpand(
208  $this->algorithm,
209  $this->prk,
210  $CTXinfo,
211  $bytes,
212  $this->lastK
213  );
214  }
215 
245  public static function HKDF( $hash, $ikm, $salt, $info, $L ) {
246  $prk = self::HKDFExtract( $hash, $salt, $ikm );
247  $okm = self::HKDFExpand( $hash, $prk, $info, $L );
248  return $okm;
249  }
250 
261  private static function HKDFExtract( $hash, $salt, $ikm ) {
262  return hash_hmac( $hash, $ikm, $salt, true );
263  }
264 
280  private static function HKDFExpand( $hash, $prk, $info, $bytes, &$lastK = '' ) {
281  $hashLen = MWCryptHKDF::$hashLength[$hash];
282  $rounds = ceil( $bytes / $hashLen );
283  $output = '';
284 
285  if ( $bytes > 255 * $hashLen ) {
286  throw new MWException( "Too many bytes requested from HDKFExpand" );
287  }
288 
289  // K(1) = HMAC(PRK, CTXinfo || 1);
290  // K(i) = HMAC(PRK, K(i-1) || CTXinfo || i); 1 < i <= t;
291  for ( $counter = 1; $counter <= $rounds; ++$counter ) {
292  $lastK = hash_hmac(
293  $hash,
294  $lastK . $info . chr( $counter ),
295  $prk,
296  true
297  );
298  $output .= $lastK;
299  }
300 
301  return substr( $output, 0, $bytes );
302  }
303 
311  public static function generate( $bytes, $context ) {
312  return self::singleton()->realGenerate( $bytes, $context );
313  }
314 
323  public static function generateHex( $chars, $context = '' ) {
324  $bytes = ceil( $chars / 2 );
325  $hex = bin2hex( self::singleton()->realGenerate( $bytes, $context ) );
326  return substr( $hex, 0, $chars );
327  }
328 
329 }
$context
a "context information" string CTXinfo (which may be null) See http://eprint.iacr.org/2010/264.pdf Section 4.1
Definition: MWCryptHKDF.php:80
static generateHex($chars, $context= '')
Generate cryptographically random data and return it in hexadecimal string format.
static $hashLength
Round count is computed based on the hash'es output length, which neither php nor openssl seem to pro...
Definition: MWCryptHKDF.php:86
$cache
The persistant cache.
Definition: MWCryptHKDF.php:43
__destruct()
Save the last block generated, so the next user will compute a different PRK from the same SKM...
$salt
binary string, the salt for the HKDF
Definition: MWCryptHKDF.php:58
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
$skm
The secret key material.
Definition: MWCryptHKDF.php:69
you have access to all of the normal MediaWiki so you can get a DB use the cache
Definition: maintenance.txt:52
$cacheKey
Cache key we'll use for our salt.
Definition: MWCryptHKDF.php:48
$algorithm
The hash algorithm being used.
Definition: MWCryptHKDF.php:53
static HKDF($hash, $ikm, $salt, $info, $L)
RFC5869 defines HKDF in 2 steps, extraction and expansion.
static __construct($secretKeyMaterial, $algorithm, $cache, $context)
$prk
The pseudorandom key.
Definition: MWCryptHKDF.php:63
$lastK
The last block (K(i)) of the most recent expanded key.
Definition: MWCryptHKDF.php:74
CACHE_MEMCACHED $wgMainCacheType
Definition: memcached.txt:63
static singleton()
Return a singleton instance, based on the global configs.
getSaltUsingCache()
MW specific salt, cached from last run.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1004
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
static HKDFExpand($hash, $prk, $info, $bytes, &$lastK= '')
Expand the key with the given context.
static getLocalServerInstance($fallback=CACHE_NONE)
Factory function for CACHE_ACCEL (referenced from DefaultSettings.php)
static HKDFExtract($hash, $salt, $ikm)
Extract the PRK, PRK = HMAC(XTS, SKM) Note that the hmac is keyed with XTS (the salt), and the SKM (source key material) is the "data".
realGenerate($bytes, $context= '')
Produce $bytes of secure random data.
static generate($bytes, $context)
Generate cryptographically random data and return it in raw binary form.
wfMemcKey()
Make a cache key for the local wiki.
static generate($bytes, $forceStrong=false)
Generate a run of (ideally) cryptographically random data and return it in raw binary form...
static $singleton
Singleton instance for public use.
Definition: MWCryptHKDF.php:38