MediaWiki  REL1_24
MWCryptHKDF.php
Go to the documentation of this file.
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 }