Completed
Push — master ( e25e6d...0bf370 )
by Michael
04:55 queued 02:22
created

Hash::verify()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 18
ccs 7
cts 7
cp 1
rs 9.4285
c 1
b 0
f 0
cc 2
eloc 7
nc 2
nop 3
crap 2
1
<?php
2
3
/**
4
 * Hash.php
5
 * 
6
 * PHP version 5
7
 * 
8
 * @category Dcrypt
9
 * @package  Dcrypt
10
 * @author   Michael Meyer (mmeyer2k) <[email protected]>
11
 * @license  http://opensource.org/licenses/MIT The MIT License (MIT)
12
 * @link     https://github.com/mmeyer2k/dcrypt
13
 */
14
15
namespace Dcrypt;
16
17
/**
18
 * An opaque 512 bit iterative hash function.
19
 * 
20
 * 16 bytes => iv
21
 * 12 bytes => cost checksum
22
 *  4 bytes => cost
23
 * 32 bytes => hmac
24
 * 
25
 * ivivivivivivivivsssssssssssscosthmachmachmachmachmachmachmachmac
26
 * 
27
 * @category Dcrypt
28
 * @package  Dcrypt
29
 * @author   Michael Meyer (mmeyer2k) <[email protected]>
30
 * @license  http://opensource.org/licenses/MIT The MIT License (MIT)
31
 * @link     https://github.com/mmeyer2k/dcrypt
32
 * @link     https://apigen.ci/github/mmeyer2k/dcrypt/namespace-Dcrypt.html
33
 */
34
final class Hash extends Support
35
{
36
37
    const ALGO = 'sha256';
38
39
    /**
40
     * Internal function used to build the actual hash.
41
     *  
42
     * @param string       $input    Data to hash
43
     * @param string       $password Password to use in HMAC call
44
     * @param integer      $cost     Number of iterations to use
45
     * @param string|null  $salt     Initialization vector to use in HMAC calls
46
     * @return string
47
     */
48 5
    private static function build($input, $password, $cost, $salt = null)
49
    {
50
        // Generate salt if needed
51 5
        $salt = $salt === null ? Random::bytes(16) : $salt;
52
53
        // Verify and normalize cost value
54 5
        $cost = self::cost($cost);
55
56
        // Create key to use for hmac operations
57 5
        $key = \hash_hmac(self::ALGO, $salt, $password, true);
58
59
        // Perform hash iterations. Get a 32 byte output value
60 5
        $hash = self::ihmac($input, $key, $cost, self::ALGO);
61
62
        // Return the salt + cost blob + hmac
63 5
        return $salt . self::costHash($cost, $salt, $password) . $hash;
64
    }
65
66
    /**
67
     * Return a normalized cost value.
68
     * 
69
     * @param int $cost Number of iterations to use.
70
     * 
71
     * @return int
72
     */
73 5
    private static function cost($cost)
74
    {
75 5
        return $cost % \pow(2, 32);
76
    }
77
78 5
    private static function costHash($cost, $salt, $password)
79
    {
80
        // Hash and return first 12 bytes
81 5
        $hash = Str::substr(\hash_hmac(self::ALGO, $cost, $salt, true), 0, 12);
82
83
        // Convert cost to base 256 then encrypt with OTP stream cipher
84 5
        $cost = Otp::crypt(self::dec2bin($cost), $password);
85
86 5
        return $hash . $cost;
87
    }
88
89
    /**
90
     * Perform a raw iterative HMAC operation with a configurable algo.
91
     * 
92
     * This class always performs at least one hash to prevent the input from
93
     * being passed back unchanged if bad parameters are set.
94
     * 
95
     * @param string  $data Data to hash.
96
     * @param string  $key  Key to use to authenticate the hash.
97
     * @param integer $iter Number of times to iteratate the hash
98
     * @param string  $algo Name of algo (sha256 or sha512 recommended)
99
     * 
100
     * @return string
101
     */
102 16
    public static function ihmac($data, $key, $iter, $algo = 'sha256')
103
    {
104 16
        $iter = abs($iter);
105
        
106 16
        for ($i = 0; $i <= $iter; $i++) {
107 16
            $data = \hash_hmac($algo, $data . $i . $iter, $key, true);
108 16
        }
109
        
110 16
        if ($data === false) {
111
            throw new \exception("$algo is not supported by hash_hmac");
112
        }
113
114 16
        return $data;
115
    }
116
117
    /**
118
     * Hash an input string into a salted 512 byte hash.
119
     * 
120
     * @param string  $input    Data to hash.
121
     * @param string  $password HMAC validation password.
122
     * @param integer $cost     Cost value of the hash.
123
     * 
124
     * @return string
125
     */
126 4
    public static function make($input, $password, $cost = 250000)
127
    {
128 4
        return self::build($input, $password, $cost, null);
129
    }
130
131
    /**
132
     * Check the validity of a hash.
133
     * 
134
     * @param string $input    Input to test.
135
     * @param string $hash     Known hash to validate against.
136
     * @param string $password HMAC password to use during iterative hash. 
137
     * 
138
     * @return boolean
139
     */
140 3
    public static function verify($input, $hash, $password)
141
    {
142
        // Get the salt value from the decrypted prefix
143 3
        $salt = Str::substr($hash, 0, 16);
144
145
        // Get the encrypted cost bytes
146 3
        $cost = self::bin2dec(Otp::crypt(Str::substr($hash, 28, 4), $password));
147
148
        // Get the entire cost+hash blob for comparison
149 3
        $blob = Str::substr($hash, 16, 16);
150
151 3
        if (!Str::equal(self::costHash($cost, $salt, $password), $blob)) {
152 1
            return false;
153
        }
154
155
        // Return the boolean equivalence
156 3
        return Str::equal($hash, self::build($input, $password, $cost, $salt));
157
    }
158
159
}
160