Completed
Push — master ( fe42e4...e8f634 )
by Michael
02:12
created

Hash::bin2dec()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
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 480 bit / 60 byte iterative hash function.
19
 *
20
 * 16 bytes => iv
21
 *  8 bytes => cost hash
22
 *  4 bytes => cost
23
 * 32 bytes => hmac
24
 *
25
 * Byte format:
26
 *
27
 *                  costhash    hmachmachmachmachmachmachmachmac
28
 * |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
29
 * iviviviviviviviv         cost
30
 *
31
 * @category Dcrypt
32
 * @package  Dcrypt
33
 * @author   Michael Meyer (mmeyer2k) <[email protected]>
34
 * @license  http://opensource.org/licenses/MIT The MIT License (MIT)
35
 * @link     https://github.com/mmeyer2k/dcrypt
36
 * @link     https://apigen.ci/github/mmeyer2k/dcrypt/namespace-Dcrypt.html
37
 */
38
class Hash
39
{
40
    const ALGO = 'sha256';
41
42
    /**
43
     * Internal function used to build the actual hash.
44
     *
45
     * @param string      $data Data to hash
46
     * @param string      $pass Password to use in HMAC call
47 5
     * @param int         $cost Number of iterations to use
48
     * @param string|null $salt Initialization vector to use in HMAC calls
49
     * @return string
50 5
     */
51
    private static function build(string $data, string $pass, int $cost, string $salt = null): string
52
    {
53 5
        // Generate salt if needed
54
        $salt = $salt ?? \random_bytes(16);
55
56 5
        // Generate a deterministic hash of the password
57
        $pkey = \hash_pbkdf2(self::ALGO, $pass, $salt, $cost, 0, true);
58
59 5
        // HMAC the input parameter with the generated key
60
        $hash = \hash_hmac(self::ALGO, $data, $pkey, true);
61
62 5
        // Covert cost value to byte array and encrypt
63
        $cost = self::costEncrypt($cost, $salt, $pass);
64
65
        // Create a hash of the cost to prevent DOS attacks caused by
66
        // flipping bits in the cost area of the blob and then requesting validation
67
        $chsh = self::costHash($cost, $pass);
68
69
        // Return the salt + cost + hmac as a single string
70
        return $salt . $chsh . $cost . $hash;
71
    }
72 5
73
    /**
74 5
     * Encrypts the cost value so that it can be added to the output hash discretely
75
     *
76
     * @param int    $cost
77 5
     * @param string $salt
78
     * @param string $pass
79
     * @return string
80 5
     */
81
    private static function costEncrypt(int $cost, string $salt, string $pass): string
82
    {
83 5
        // Pack the cost value into a 4 byte string
84
        $packed = pack('N', $cost);
85 5
86
        // Encrypt the string with the Otp stream cipher
87
        return Otp::crypt($packed, ($pass . $salt), self::ALGO);
88
    }
89
90
    /**
91
     * Decrypts the cost string back into an int
92
     *
93
     * @param string $pack
94
     * @param string $salt
95
     * @param string $pass
96
     * @return int
97
     */
98
    private static function costDecrypt(string $pack, string $salt, string $pass): int
99
    {
100
        // Decrypt the cost value stored in the 32bit int
101 16
        $pack = Otp::crypt($pack, ($pass . $salt), self::ALGO);
102
103 16
        // Unpack the value back to an integer and return to caller
104
        return unpack('N', $pack)[1];
105 16
    }
106 16
107 16
    /**
108
     * Hash an input string into a salted 52 bit hash.
109 16
     *
110
     * @param string $data Data to hash.
111
     * @param string $pass HMAC validation password.
112
     * @param int    $cost Cost value of the hash.
113 16
     * @return string
114
     */
115
    public static function make(string $data, string $pass, int $cost = 250000): string
116
    {
117
        return self::build($data, $pass, $cost, null);
118
    }
119
120
    /**
121
     * Check the validity of a hash.
122
     *
123
     * @param string $data Input to test.
124
     * @param string $hash Known hash to validate against.
125 4
     * @param string $pass HMAC password to use during iterative hash.
126
     * @return boolean
127 4
     */
128
    public static function verify(string $data, string $hash, string $pass): bool
129
    {
130
        // Get the salt value from the decrypted prefix
131
        $salt = Str::substr($hash, 0, 16);
132
133
        // Get the encrypted cost bytes out of the blob
134
        $chsh = Str::substr($hash, 16, 8);
135
136
        // Get the encrypted cost bytes out of the blob
137
        $cost = Str::substr($hash, 24, 4);
138
139 3
        // If the provided cost hash does not calculate to be the same as the one provided then consider the hash invalid.
140
        if ($chsh !== self::costHash($cost, $pass)) {
141
            return false;
142 3
        }
143
144
        // Decrypt the cost value stored in the 32bit int
145 3
        $cost = self::costDecrypt($cost, $salt, $pass);
146
147
        // Build a hash from the input for comparison
148 3
        $calc = self::build($data, $pass, $cost, $salt);
149
150 3
        // Return the boolean equivalence
151 1
        return Str::equal($hash, $calc);
152
    }
153
154
    /**
155 3
     * Returns the correct hash for an encrypted cost value.
156
     *
157
     * @param string $cost
158
     * @param string $pass
159
     * @return string
160
     */
161
    private static function costHash(string $cost, string $pass): string
162
    {
163
        return Str::substr(\hash_hmac(self::ALGO, $cost, $pass, true), 0, 8);
164
    }
165
}
166