Completed
Pull Request — 2.x (#82)
by Hari
04:17 queued 02:38
created

HtpasswdVerifier::sha()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
nc 1
cc 1
eloc 4
nop 2
crap 1
1
<?php
2
/**
3
 *
4
 * This file is part of Aura for PHP.
5
 *
6
 * @license http://opensource.org/licenses/bsd-license.php BSD
7
 *
8
 */
9
namespace Aura\Auth\Verifier;
10
11
/**
12
 *
13
 * Verfies passwords from htpasswd files; supports APR1/MD5, DES, SHA1, and
14
 * Bcrypt.
15
 *
16
 * The APR1/MD5 implementation was originally written by Mike Wallner
17
 * <[email protected]>; any flaws are the fault of Paul M. Jones
18
 * <[email protected]>.
19
 *
20
 * @package Aura.Auth
21
 *
22
 */
23
class HtpasswdVerifier implements VerifierInterface
24
{
25
    /**
26
     *
27
     * Verifies a plaintext password against a hash.
28
     *
29
     * @param string $plaintext Plaintext password.
30
     *
31
     * @param string $hashvalue Comparison hash.
32
     *
33
     * @param array $extra Optional array if used by verify.
34
     *
35
     * @return bool
36
     *
37
     */
38 5
    public function verify($plaintext, $hashvalue, array $extra = array())
39
    {
40 5
        $hashvalue = trim($hashvalue);
41
42 5
        if (substr($hashvalue, 0, 4) == '$2y$') {
43
            return password_verify($plaintext, $hashvalue);
44
        }
45
46 5
        if (substr($hashvalue, 0, 5) == '{SHA}') {
47 1
            return $this->sha($plaintext, $hashvalue);
48
        }
49
50 4
        if (substr($hashvalue, 0, 6) == '$apr1$') {
51 1
            return $this->apr1($plaintext, $hashvalue);
52
        }
53
54 3
        return $this->des($plaintext, $hashvalue);
55
    }
56
57
    /**
58
     *
59
     * Verify using SHA1 hashing.
60
     *
61
     * @param string $plaintext Plaintext password.
62
     *
63
     * @param string $hashvalue Comparison hash.
64
     *
65
     * @return bool
66
     *
67
     */
68 1
    protected function sha($plaintext, $hashvalue)
69
    {
70 1
        $hex = sha1($plaintext, true);
71 1
        $computed_hash = '{SHA}' . base64_encode($hex);
72 1
        return $computed_hash === $hashvalue;
73
    }
74
75
    /**
76
     *
77
     * Verify using APR compatible MD5 hashing.
78
     *
79
     * @param string $plaintext Plaintext password.
80
     *
81
     * @param string $hashvalue Comparison hash.
82
     *
83
     * @return bool
84
     *
85
     */
86 1
    protected function apr1($plaintext, $hashvalue)
87
    {
88 1
        $salt = preg_replace('/^\$apr1\$([^$]+)\$.*/', '\\1', $hashvalue);
89 1
        $context = $this->computeContext($plaintext, $salt);
90 1
        $binary = $this->computeBinary($plaintext, $salt, $context);
91 1
        $p = $this->computeP($binary);
92 1
        $computed_hash = '$apr1$' . $salt . '$' . $p
93 1
                       . $this->convert64(ord($binary[11]), 3);
94 1
        return $computed_hash === $hashvalue;
95
    }
96
97
    /**
98
     *
99
     * Compute the context.
100
     *
101
     * @param string $plaintext Plaintext password.
102
     *
103
     * @param string $salt The salt.
104
     *
105
     * @return string
106
     *
107
     */
108 1
    protected function computeContext($plaintext, $salt)
109
    {
110 1
        $length = strlen($plaintext);
111 1
        $hash = hash('md5', $plaintext . $salt . $plaintext, true);
112 1
        $context = $plaintext . '$apr1$' . $salt;
113
114 1
        for ($i = $length; $i > 0; $i -= 16) {
115 1
            $context .= substr($hash, 0, min(16, $i));
116 1
        }
117
118 1
        for ($i = $length; $i > 0; $i >>= 1) {
119 1
            $context .= ($i & 1) ? chr(0) : $plaintext[0];
120 1
        }
121
122 1
        return $context;
123
    }
124
125
    /**
126
     *
127
     * Compute the binary.
128
     *
129
     * @param string $plaintext Plaintext password.
130
     *
131
     * @param string $salt The salt.
132
     *
133
     * @param string $context The context.
134
     *
135
     * @return string
136
     *
137
     */
138 1
    protected function computeBinary($plaintext, $salt, $context)
139
    {
140 1
        $binary = hash('md5', $context, true);
141 1
        for ($i = 0; $i < 1000; $i++) {
142 1
            $new = ($i & 1) ? $plaintext : $binary;
143 1
            if ($i % 3) {
144 1
                $new .= $salt;
145 1
            }
146 1
            if ($i % 7) {
147 1
                $new .= $plaintext;
148 1
            }
149 1
            $new .= ($i & 1) ? $binary : $plaintext;
150 1
            $binary = hash('md5', $new, true);
151 1
        }
152 1
        return $binary;
153
    }
154
155
    /**
156
     *
157
     * Compute the P value for a binary.
158
     *
159
     * @param string $binary The binary.
160
     *
161
     * @return string
162
     *
163
     */
164 1
    protected function computeP($binary)
165
    {
166 1
        $p = array();
167 1
        for ($i = 0; $i < 5; $i++) {
168 1
            $k = $i + 6;
169 1
            $j = $i + 12;
170 1
            if ($j == 16) {
171 1
                $j = 5;
172 1
            }
173 1
            $p[] = $this->convert64(
174 1
                (ord($binary[$i]) << 16) |
175 1
                (ord($binary[$k]) << 8) |
176 1
                (ord($binary[$j])),
177
                5
178 1
            );
179 1
        }
180 1
        return implode($p);
181
    }
182
183
    /**
184
     *
185
     * Convert to allowed 64 characters for encryption.
186
     *
187
     * @param string $value The value to convert.
188
     *
189
     * @param int $count The number of characters.
190
     *
191
     * @return string The converted value.
192
     *
193
     */
194 1
    protected function convert64($value, $count)
195
    {
196 1
        $charset = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
197 1
        $result = '';
198 1
        while (--$count) {
199 1
            $result .= $charset[$value & 0x3f];
200 1
            $value >>= 6;
201 1
        }
202 1
        return $result;
203
    }
204
205
    /**
206
     *
207
     * Verify using DES hashing.
208
     *
209
     * Note that crypt() will only check up to the first 8
210
     * characters of a password; chars after 8 are ignored. This
211
     * means that if the real password is "atecharsnine", the
212
     * word "atechars" would be valid.  This is bad.  As a
213
     * workaround, if the password provided by the user is
214
     * longer than 8 characters, this method will *not* verify
215
     * it.
216
     *
217
     * @param string $plaintext Plaintext password.
218
     *
219
     * @param string $hashvalue Comparison hash.
220
     *
221
     * @return bool
222
     *
223
     */
224 3
    protected function des($plaintext, $hashvalue)
225
    {
226 3
        if (strlen($plaintext) > 8) {
227 1
            return false;
228
        }
229
230 3
        $computed_hash = crypt($plaintext, $hashvalue);
231 3
        return $computed_hash === $hashvalue;
232
    }
233
}
234