@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow hashers to side-grade hashes across cost settings

Summary:
Ref T4443. In addition to performing upgrades from, e.g., md5 -> bcrypt, also allow sidegrades from, e.g., bcrypt(cost=11) to bcrypt(cost=12). This allows us to, for example, bump the cost function every 18 months and stay on par with Moore's law, on average.

I'm also allowing "upgrades" which technically reduce cost, but this seems like the right thing to do (i.e., generally migrate password storage so it's all uniform, on average).

Test Plan:
- Fiddled the bcrypt cost function and saw appropriate upgrade UI, and upgraded passwords upon password change.
- Passwords still worked.
- Around cost=13 or 14 things start getting noticibly slow, so bcrypt does actually work. Such wow.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T4443

Differential Revision: https://secure.phabricator.com/D8271

+55 -6
+24 -5
src/infrastructure/util/password/PhabricatorBcryptPasswordHasher.php
··· 34 34 protected function getPasswordHash(PhutilOpaqueEnvelope $envelope) { 35 35 $raw_input = $envelope->openEnvelope(); 36 36 37 - // NOTE: The default cost is "10", but my laptop can do a hash of cost 38 - // "12" in about 300ms. Since server hardware is often virtualized or old, 39 - // just split the difference. 40 - 41 37 $options = array( 42 - 'cost' => 11, 38 + 'cost' => $this->getBcryptCost(), 43 39 ); 44 40 45 41 $raw_hash = password_hash($raw_input, CRYPT_BLOWFISH, $options); ··· 51 47 PhutilOpaqueEnvelope $password, 52 48 PhutilOpaqueEnvelope $hash) { 53 49 return password_verify($password->openEnvelope(), $hash->openEnvelope()); 50 + } 51 + 52 + protected function canUpgradeInternalHash(PhutilOpaqueEnvelope $hash) { 53 + $info = password_get_info($hash->openEnvelope()); 54 + 55 + // NOTE: If the costs don't match -- even if the new cost is lower than 56 + // the old cost -- count this as an upgrade. This allows costs to be 57 + // adjusted down and hashing to be migrated toward the new cost if costs 58 + // are ever configured too high for some reason. 59 + 60 + $cost = idx($info['options'], 'cost'); 61 + if ($cost != $this->getBcryptCost()) { 62 + return true; 63 + } 64 + 65 + return false; 66 + } 67 + 68 + private function getBcryptCost() { 69 + // NOTE: The default cost is "10", but my laptop can do a hash of cost 70 + // "12" in about 300ms. Since server hardware is often virtualized or old, 71 + // just split the difference. 72 + return 11; 54 73 } 55 74 56 75 }
+31 -1
src/infrastructure/util/password/PhabricatorPasswordHasher.php
··· 130 130 } 131 131 132 132 133 + /** 134 + * Check if an existing hash created by this algorithm is upgradeable. 135 + * 136 + * The default implementation returns `false`. However, hash algorithms which 137 + * have (for example) an internal cost function may be able to upgrade an 138 + * existing hash to a stronger one with a higher cost. 139 + * 140 + * @param PhutilOpaqueEnvelope Bare hash. 141 + * @return bool True if the hash can be upgraded without 142 + * changing the algorithm (for example, to a 143 + * higher cost). 144 + * @task hasher 145 + */ 146 + protected function canUpgradeInternalHash(PhutilOpaqueEnvelope $hash) { 147 + return false; 148 + } 149 + 150 + 133 151 /* -( Using Hashers )------------------------------------------------------ */ 134 152 135 153 ··· 318 336 $current_hasher = self::getHasherForHash($hash); 319 337 $best_hasher = self::getBestHasher(); 320 338 321 - return ($current_hasher->getHashName() != $best_hasher->getHashName()); 339 + if ($current_hasher->getHashName() != $best_hasher->getHashName()) { 340 + // If the algorithm isn't the best one, we can upgrade. 341 + return true; 342 + } 343 + 344 + $info = self::parseHashFromStorage($hash); 345 + if ($current_hasher->canUpgradeInternalHash($info['hash'])) { 346 + // If the algorithm provides an internal upgrade, we can also upgrade. 347 + return true; 348 + } 349 + 350 + // Already on the best algorithm with the best settings. 351 + return false; 322 352 } 323 353 324 354