MediaWiki  master
CryptHKDF.php
Go to the documentation of this file.
1 <?php
33 class CryptHKDF {
34 
38  protected $cache = null;
39 
43  protected $cacheKey = null;
44 
48  protected $algorithm = null;
49 
54  protected $salt = '';
55 
59  private $prk = '';
60 
67  private $skm;
68 
72  protected $lastK;
73 
80  protected $context = [];
81 
88  public static $hashLength = [
89  'md5' => 16,
90  'sha1' => 20,
91  'sha224' => 28,
92  'sha256' => 32,
93  'sha384' => 48,
94  'sha512' => 64,
95  'ripemd128' => 16,
96  'ripemd160' => 20,
97  'ripemd256' => 32,
98  'ripemd320' => 40,
99  'whirlpool' => 64,
100  ];
101 
109  public function __construct( $secretKeyMaterial, $algorithm, BagOStuff $cache, $context ) {
110  if ( strlen( $secretKeyMaterial ) < 16 ) {
111  throw new InvalidArgumentException( "secret was too short." );
112  }
113  $this->skm = $secretKeyMaterial;
114  $this->algorithm = $algorithm;
115  $this->cache = $cache;
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 = $cache->makeKey( 'HKDF', mt_rand( 0, 16 ) );
122  }
123 
129  public 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 random_bytes(), 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 = random_bytes( 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 
165  public function generate( $bytes, $context = '' ) {
166  if ( $this->prk === '' ) {
167  $salt = $this->getSaltUsingCache();
168  $this->prk = self::HKDFExtract(
169  $this->algorithm,
170  $salt,
171  $this->skm
172  );
173  }
174 
175  $CTXinfo = implode( ':', array_merge( $this->context, [ $context ] ) );
176 
177  return self::HKDFExpand(
178  $this->algorithm,
179  $this->prk,
180  $CTXinfo,
181  $bytes,
182  $this->lastK
183  );
184  }
185 
215  public static function HKDF( $hash, $ikm, $salt, $info, $L ) {
216  $prk = self::HKDFExtract( $hash, $salt, $ikm );
217  $okm = self::HKDFExpand( $hash, $prk, $info, $L );
218  return $okm;
219  }
220 
231  private static function HKDFExtract( $hash, $salt, $ikm ) {
232  return hash_hmac( $hash, $ikm, $salt, true );
233  }
234 
250  private static function HKDFExpand( $hash, $prk, $info, $bytes, &$lastK = '' ) {
251  $hashLen = self::$hashLength[$hash];
252  $rounds = ceil( $bytes / $hashLen );
253  $output = '';
254 
255  if ( $bytes > 255 * $hashLen ) {
256  throw new InvalidArgumentException( 'Too many bytes requested from HDKFExpand' );
257  }
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 
271  return substr( $output, 0, $bytes );
272  }
273 }
CryptHKDF\getSaltUsingCache
getSaltUsingCache()
MW specific salt, cached from last run.
Definition: CryptHKDF.php:139
CryptHKDF\HKDFExpand
static HKDFExpand( $hash, $prk, $info, $bytes, &$lastK='')
Expand the key with the given context.
Definition: CryptHKDF.php:250
BagOStuff
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:65
CryptHKDF\HKDFExtract
static HKDFExtract( $hash, $salt, $ikm)
Extract the PRK, PRK = HMAC(XTS, SKM) Note that the hmac is keyed with XTS (the salt),...
Definition: CryptHKDF.php:231
CryptHKDF\$salt
string $salt
binary string, the salt for the HKDF
Definition: CryptHKDF.php:54
CryptHKDF\__construct
__construct( $secretKeyMaterial, $algorithm, BagOStuff $cache, $context)
Definition: CryptHKDF.php:109
CryptHKDF\generate
generate( $bytes, $context='')
Produce $bytes of secure random data.
Definition: CryptHKDF.php:165
CryptHKDF\HKDF
static HKDF( $hash, $ikm, $salt, $info, $L)
RFC5869 defines HKDF in 2 steps, extraction and expansion.
Definition: CryptHKDF.php:215
CryptHKDF
Definition: CryptHKDF.php:33
CryptHKDF\$hashLength
static int[] $hashLength
Round count is computed based on the hash'es output length, which neither php nor openssl seem to pro...
Definition: CryptHKDF.php:88
CryptHKDF\$cache
BagOStuff $cache
The persistent cache.
Definition: CryptHKDF.php:38
CryptHKDF\$algorithm
string $algorithm
The hash algorithm being used.
Definition: CryptHKDF.php:48
BagOStuff\makeKey
makeKey( $class,... $components)
Make a cache key, scoped to this instance's keyspace.
CryptHKDF\$prk
string $prk
The pseudorandom key.
Definition: CryptHKDF.php:59
CryptHKDF\__destruct
__destruct()
Save the last block generated, so the next user will compute a different PRK from the same SKM.
Definition: CryptHKDF.php:129
CryptHKDF\$context
array $context
a "context information" string CTXinfo (which may be null) See http://eprint.iacr....
Definition: CryptHKDF.php:80
CryptHKDF\$lastK
string $lastK
The last block (K(i)) of the most recent expanded key.
Definition: CryptHKDF.php:72
CryptHKDF\$skm
string $skm
The secret key material.
Definition: CryptHKDF.php:67
CryptHKDF\$cacheKey
string $cacheKey
Cache key we'll use for our salt.
Definition: CryptHKDF.php:43