Completed
Push — master ( 931a08...25c02a )
by Michael
01:59
created

Hash::bin2dec()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * Hash.php
5
 *
6
 * PHP version 7
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
35
{
36
    const ALGO = 'sha256';
37
38
    /**
39
     * Internal function used to build the actual hash.
40
     *
41
     * @param string       $input    Data to hash
42
     * @param string       $password Password to use in HMAC call
43
     * @param int          $cost     Number of iterations to use
44
     * @param string|null  $salt     Initialization vector to use in HMAC calls
45
     *
46
     * @return string
47 5
     */
48
    private static function build(string $input, string $password, int $cost, string $salt = null): string
49
    {
50 5
        // Generate salt if needed
51
        $salt = $salt ?? \random_bytes(16);
52
53 5
        // Verify and normalize cost value
54
        $cost = self::cost($cost);
55
56 5
        // Create key to use for hmac operations
57
        $key = self::hmac($salt, $password, self::ALGO);
58
59 5
        // Perform hash iterations. Get a 32 byte output value
60
        $hash = self::ihmac($input, $key, $cost, self::ALGO);
61
62 5
        // Return the salt + cost blob + hmac
63
        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 5
     */
73
    private static function cost(int $cost): int
74 5
    {
75
        return $cost % \pow(2, 32);
76
    }
77 5
78
    private static function costHash(int $cost, string $salt, string $password): string
79
    {
80 5
        // Hash and return first 12 bytes
81
        $hash = Str::substr(self::hmac($cost, $salt, self::ALGO), 0, 12);
82
83 5
        // Convert cost to base 256 then encrypt with OTP stream cipher
84
        $cost = Otp::crypt(self::dec2bin($cost), $password);
85 5
86
        return $hash . $cost;
87
    }
88
89
    /**
90
     * Perform a raw iterative HMAC operation with a configurable algo.
91
     *
92
     * @param string  $data Data to hash.
93
     * @param string  $key  Key to use to authenticate the hash.
94
     * @param int     $iter Number of times to iteratate the hash
95
     * @param string  $algo Name of algo (sha256 or sha512 recommended)
96
     *
97
     * @return string
98
     */
99
    public static function ihmac(string $data, string $key, int $iter, string $algo = 'sha256'): string
100
    {
101 16
        // Can't perform negative iterations
102
        $iter = abs($iter);
103 16
104
        // Perform iterative hmac calls
105 16
        // Make sure $iter value of 0 is handled
106 16
        for ($i = 0; $i <= $iter; $i++) {
107 16
            $data = self::hmac($data . $i . $iter, $key, $algo);
108
        }
109 16
110
        return $data;
111
    }
112
113 16
    /**
114
     * Perform a single hmac iteration. This adds an extra layer of safety because hash_hmac can return false if algo
115
     * is not valid. Return type hint will throw an exception if this happens.
116
     *
117
     * @param string  $data Data to hash.
118
     * @param string  $key  Key to use to authenticate the hash.
119
     * @param string  $algo Name of algo (sha256 is default)
120
     *
121
     * @return string
122
     */
123
    public static function hmac(string $data, string $key, string $algo): string
124
    {
125 4
        return \hash_hmac($algo, $data, $key, true);
126
    }
127 4
128
    /**
129
     * Hash an input string into a salted 512 byte hash.
130
     *
131
     * @param string  $input    Data to hash.
132
     * @param string  $password HMAC validation password.
133
     * @param int     $cost     Cost value of the hash.
134
     *
135
     * @return string
136
     */
137
    public static function make(string $input, string $password, int $cost = 250000): string
138
    {
139 3
        return self::build($input, $password, $cost, null);
140
    }
141
142 3
    /**
143
     * Check the validity of a hash.
144
     *
145 3
     * @param string $input    Input to test.
146
     * @param string $hash     Known hash to validate against.
147
     * @param string $password HMAC password to use during iterative hash.
148 3
     *
149
     * @return boolean
150 3
     */
151 1
    public static function verify(string $input, string $hash, string $password): bool
152
    {
153
        // Get the salt value from the decrypted prefix
154
        $salt = Str::substr($hash, 0, 16);
155 3
156
        // Get the encrypted cost bytes
157
        $cost = self::bin2dec(Otp::crypt(Str::substr($hash, 28, 4), $password));
158
159
        // Get the entire cost+hash blob for comparison
160
        $blob = Str::substr($hash, 16, 16);
161
162
        if (!Str::equal(self::costHash($cost, $salt, $password), $blob)) {
163
            return false;
164
        }
165
166
        // Return the boolean equivalence
167
        return Str::equal($hash, self::build($input, $password, $cost, $salt));
168
    }
169
170
    /**
171
     * Turns an integer into a 4 byte binary representation
172
     *
173
     * @param int $dec Integer to convert to binary
174
     *
175
     * @return string
176
     */
177
    private static function dec2bin(int $dec): string
178
    {
179
        return hex2bin(\str_pad(\dechex($dec), 8, '0', STR_PAD_LEFT));
180
    }
181
182
    /**
183
     * Reverses dec2bin
184
     *
185
     * @param string $bin Binary string to convert to decimal
186
     *
187
     * @return string
188
     */
189
    private static function bin2dec(string $bin): string
190
    {
191
        return \hexdec(\bin2hex($bin));
192
    }
193
}
194