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 hash
116 // 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 $rounds
130 * @return $this
131 */
132 public function setRounds($rounds)
133 {
134 $this->rounds = (int)$rounds;
135 return $this;
136 }
137
138}
∎