Laravel: Implementing a CRYPT_SHA256 Hasher

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 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}

Some absolutely amazing
people

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.