November 30, 2016 —John Koster
In this article we will create an implementation of Illuminate\Contracts\Hashing\Hasher
using PHP's crypt
function and the CRYPT_SHA256
hashing function. Like in the previous sections, we will examine each method before looking at the full implementation.
make($value, array $options = [])
The make
method is responsible for doing the actual hashing. It accepts an $options
array. We will allow a sixteen character long salt to be supplied using the options array. If no salt is supplied, we will generate one. We will also accept a rounds option, to control the number of iterations the hashing function will perform.
For the actual hashing, we will make a call to PHP's crypt
function.
public function check($value, $hashedValue, array $options = [])
The check
method is used to check that a known $value
is the same as a given $hashedValue
. We will not need any options for this method.
For the actual hashing, we will use PHP's crypt
function.
public function needsRehash($hashedValue, array $options = [])
The needsRehash
method is used to determine if a given hash needs to be updated, or rehashed. We will accept a salt as an option. If the salt from the $options
array does not match the salt from the $hashedValue
the $hashedValue
, or if the rounds from the $options
array does not match the rounds from the $hashedValue
, the $hashedValue
needs to be rehashed.
Sha256Hasher Option | Description |
---|---|
salt |
Users can supply their own four character salt to the make and needsRehash methods. If no salt is supplied for the make method, one will be generated. |
rounds |
Users can give an integer for the make and needsRehash methods to change the number of iterations the hashing function will perform. |
The following is the complete implementation of the Sha256Hasher
class:
1<?php 2 3namespace Laravel\Artisan\Hashing; 4 5use RuntimeException; 6use Illuminate\Contracts\Hashing\Hasher as HasherContract; 7use Illuminate\Support\Str; 8 9class Sha256Hasher implements HasherContract 10{ 11 12 const FAILED_HASH = '*0'; 13 14 /** 15 * The prefix expected by the crypt function. 16 * 17 * @var string 18 */ 19 protected $shaPrefix = '5'; 20 21 /** 22 * Default number of rounds. 23 * 24 * @var int 25 */ 26 protected $rounds = 5000; 27 28 /** 29 * Hash the given value. 30 * 31 * @param string $value 32 * @param array $options 33 * @return string 34 * 35 * @throws \RuntimeException 36 */ 37 public function make($value, array $options = []) 38 { 39 // If the user supplied a salt, use that. If not we 40 // can generate it using the Str helper methods. 41 $salt = isset($options['salt']) ? 42 $options['salt'] : Str::random(16); 43 44 $rounds = isset($options['rounds']) ? $options['rounds'] : $this->rounds; 45 46 // Construct the SHA salt. 47 $salt = '$'.$this->shaPrefix.'$rounds='.$rounds.'$'.$salt.'$'; 48 49 $hash = crypt($value, $salt); 50 51 if ($hash == self::FAILED_HASH) { 52 // Throw an exception because the hashing failed. 53 throw new RuntimeException('SHA hashing failed.'); 54 } 55 56 return $hash; 57 } 58 59 /** 60 * Returns information about the given hash. 61 * 62 * @param string $hash 63 * @return array 64 */ 65 private function getHashInformation($hash) 66 { 67 $hashParts = explode('$', $hash); 68 return [ 69 'salt' => $hashParts[3], 70 'rounds' => (int) mb_substr($hashParts[2], 7) 71 ]; 72 } 73 74 /** 75 * Check the given plain value against a hash. 76 * 77 * @param string $value 78 * @param string $hashedValue 79 * @param array $options 80 * @return bool 81 */ 82 public function check($value, $hashedValue, array $options = []) 83 { 84 $hashParts = $this->getHashInformation($hashedValue); 85 $userValue = $this->make($value, [ 86 'salt' => $hashParts['salt'], 87 'rounds' => $hashParts['rounds']] 88 ); 89 90 return hash_equals($hashedValue, $userValue); 91 } 92 93 /** 94 * Check if the given hash has been hashed using the given options. 95 * 96 * @param string $hashedValue 97 * @param array $options 98 * @return bool 99 */100 public function needsRehash($hashedValue, array $options = [])101 {102 if (!isset($options['salt']) && !isset($options['rounds'])) {103 return false;104 }105 106 $hashParts = $this->getHashInformation($hashedValue);107 108 // If the salts differ, hash needs to be recomputed.109 if (isset($options['salt']) && $hashParts['salt']110 !== $options['salt']111 ) {112 return true;113 }114 115 // If the number of rounds differ, the hash116 // needs to be recalculated.117 if (isset($options['rounds']) && $hashParts['rounds']118 !== $options['rounds']119 ) {120 return true;121 }122 123 return false;124 }125 126 /**127 * Set the default number of rounds.128 *129 * @param int $rounds130 * @return $this131 */132 public function setRounds($rounds)133 {134 $this->rounds = (int)$rounds;135 return $this;136 }137 138}
∎
The following amazing people help support this site and my open source projects ♥️
If you're interesting in supporting my work and want to show up on this list, check out my GitHub Sponsors Profile.