MediaWiki
REL1_24
|
00001 <?php 00033 class MWCryptHKDF { 00034 00038 protected static $singleton = null; 00039 00043 protected $cache = null; 00044 00048 protected $cacheKey = null; 00049 00053 protected $algorithm = null; 00054 00058 protected $salt; 00059 00063 private $prk; 00064 00069 private $skm; 00070 00074 protected $lastK; 00075 00080 protected $context = array(); 00081 00086 public static $hashLength = array( 00087 'md5' => 16, 00088 'sha1' => 20, 00089 'sha224' => 28, 00090 'sha256' => 32, 00091 'sha384' => 48, 00092 'sha512' => 64, 00093 'ripemd128' => 16, 00094 'ripemd160' => 20, 00095 'ripemd256' => 32, 00096 'ripemd320' => 40, 00097 'whirlpool' => 64, 00098 ); 00099 00100 00107 public function __construct( $secretKeyMaterial, $algorithm, $cache, $context ) { 00108 if ( strlen( $secretKeyMaterial ) < 16 ) { 00109 throw new MWException( "MWCryptHKDF secret was too short." ); 00110 } 00111 $this->skm = $secretKeyMaterial; 00112 $this->algorithm = $algorithm; 00113 $this->cache = $cache; 00114 $this->salt = ''; // Initialize a blank salt, see getSaltUsingCache() 00115 $this->prk = ''; 00116 $this->context = is_array( $context ) ? $context : array( $context ); 00117 00118 // To prevent every call from hitting the same memcache server, pick 00119 // from a set of keys to use. mt_rand is only use to pick a random 00120 // server, and does not affect the security of the process. 00121 $this->cacheKey = wfMemcKey( 'HKDF', mt_rand( 0, 16 ) ); 00122 } 00123 00129 function __destruct() { 00130 if ( $this->lastK ) { 00131 $this->cache->set( $this->cacheKey, $this->lastK ); 00132 } 00133 } 00134 00139 protected function getSaltUsingCache() { 00140 if ( $this->salt == '' ) { 00141 $lastSalt = $this->cache->get( $this->cacheKey ); 00142 if ( $lastSalt === false ) { 00143 // If we don't have a previous value to use as our salt, we use 00144 // 16 bytes from MWCryptRand, which will use a small amount of 00145 // entropy from our pool. Note, "XTR may be deterministic or keyed 00146 // via an optional “salt value” (i.e., a non-secret random 00147 // value)..." - http://eprint.iacr.org/2010/264.pdf. However, we 00148 // use a strongly random value since we can. 00149 $lastSalt = MWCryptRand::generate( 16 ); 00150 } 00151 // Get a binary string that is hashLen long 00152 $this->salt = hash( $this->algorithm, $lastSalt, true ); 00153 } 00154 return $this->salt; 00155 } 00156 00161 protected static function singleton() { 00162 global $wgHKDFAlgorithm, $wgHKDFSecret, $wgSecretKey; 00163 00164 $secret = $wgHKDFSecret ?: $wgSecretKey; 00165 if ( !$secret ) { 00166 throw new MWException( "Cannot use MWCryptHKDF without a secret." ); 00167 } 00168 00169 // In HKDF, the context can be known to the attacker, but this will 00170 // keep simultaneous runs from producing the same output. 00171 $context = array(); 00172 $context[] = microtime(); 00173 $context[] = getmypid(); 00174 $context[] = gethostname(); 00175 00176 // Setup salt cache. Use APC, or fallback to the main cache if it isn't setup 00177 try { 00178 $cache = ObjectCache::newAccelerator( array() ); 00179 } catch ( Exception $e ) { 00180 $cache = wfGetMainCache(); 00181 } 00182 00183 if ( is_null( self::$singleton ) ) { 00184 self::$singleton = new self( $secret, $wgHKDFAlgorithm, $cache, $context ); 00185 } 00186 00187 return self::$singleton; 00188 } 00189 00197 protected function realGenerate( $bytes, $context = '' ) { 00198 00199 if ( $this->prk === '' ) { 00200 $salt = $this->getSaltUsingCache(); 00201 $this->prk = self::HKDFExtract( 00202 $this->algorithm, 00203 $salt, 00204 $this->skm 00205 ); 00206 } 00207 00208 $CTXinfo = implode( ':', array_merge( $this->context, array( $context ) ) ); 00209 00210 return self::HKDFExpand( 00211 $this->algorithm, 00212 $this->prk, 00213 $CTXinfo, 00214 $bytes, 00215 $this->lastK 00216 ); 00217 } 00218 00219 00249 public static function HKDF( $hash, $ikm, $salt, $info, $L ) { 00250 $prk = self::HKDFExtract( $hash, $salt, $ikm ); 00251 $okm = self::HKDFExpand( $hash, $prk, $info, $L ); 00252 return $okm; 00253 } 00254 00265 private static function HKDFExtract( $hash, $salt, $ikm ) { 00266 return hash_hmac( $hash, $ikm, $salt, true ); 00267 } 00268 00283 private static function HKDFExpand( $hash, $prk, $info, $bytes, &$lastK = '' ) { 00284 $hashLen = MWCryptHKDF::$hashLength[$hash]; 00285 $rounds = ceil( $bytes / $hashLen ); 00286 $output = ''; 00287 00288 if ( $bytes > 255 * $hashLen ) { 00289 throw new MWException( "Too many bytes requested from HDKFExpand" ); 00290 } 00291 00292 // K(1) = HMAC(PRK, CTXinfo || 1); 00293 // K(i) = HMAC(PRK, K(i-1) || CTXinfo || i); 1 < i <= t; 00294 for ( $counter = 1; $counter <= $rounds; ++$counter ) { 00295 $lastK = hash_hmac( 00296 $hash, 00297 $lastK . $info . chr( $counter ), 00298 $prk, 00299 true 00300 ); 00301 $output .= $lastK; 00302 } 00303 00304 return substr( $output, 0, $bytes ); 00305 } 00306 00314 public static function generate( $bytes, $context ) { 00315 return self::singleton()->realGenerate( $bytes, $context ); 00316 } 00317 00326 public static function generateHex( $chars, $context = '' ) { 00327 $bytes = ceil( $chars / 2 ); 00328 $hex = bin2hex( self::singleton()->realGenerate( $bytes, $context ) ); 00329 return substr( $hex, 0, $chars ); 00330 } 00331 00332 }