Htpasswd   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 0
dl 0
loc 107
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 5
A check() 0 15 4
A crypt() 0 3 1
A saltedSHA1() 0 4 1
A SHA1() 0 4 1
A MD5() 0 7 1
A bcrypt() 0 3 1
C cryptApr1Md5() 0 37 11
1
<?php
2
/*
3
 * This file is part of the Ariadne Component Library.
4
 *
5
 * (c) Muze <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace arc\http;
11
12
/**
13
 * This class parses a .htpasswd formatted string and extracts the users
14
 * and encrypted passwords from it.
15
 * Then you can check a username and password combination against this
16
 * list.
17
 */
18
class Htpasswd {
19
20
    public $users = [];
21
22
    /**
23
     * The constructor needs a string with the contents of a htpasswd file
24
     */
25
    public function __construct($htpasswd) {
26
        $lines = preg_split('/\r\n|\r|\n/',$htpasswd);
27
        foreach ($lines as $line) {
28
            if (strpos($line, ':')===false) {
29
                continue;
30
            }
31
            list($user,$password) = array_map('trim', explode(':',$line));
32
            if ($user && $password) {
33
                $this->users[$user] = $password;
34
            }
35
        }
36
    }
37
38
    /**
39
     * This method checks if a username and password combination matches
40
     * a user and encrypted password from the htpasswd file.
41
     * @param string $user
42
     * @param string $password
43
     * @return bool
44
     */
45
    public function check($user, $password) {
46
        if ( !isset($this->users[$user]) ) {
47
            return false;
48
        }
49
        $checks  = [ '{SSHA}' => 'saltedSHA1', '{SHA}' => 'SHA1', '$apr1$' => 'MD5', '$2y$' => 'bcrypt' ];
50
        $crypted = $this->users[$user];
51
        $check   = 'crypt';
52
        foreach($checks as $match => $algorithm) {
53
            if ( strpos($crypted, $match) === 0 ) {
54
                $check = $algorithm;
55
                break;
56
            }
57
        }
58
        return $this->$check($crypted, $password);
59
    }
60
61
    private function crypt($crypted, $password) {
62
        return (crypt( $password, substr($crypted,0,CRYPT_SALT_LENGTH) ) == $crypted);
63
    }
64
65
    private function saltedSHA1($crypted, $password) {
66
        $hash = base64_decode(substr($crypted, 6));
67
        return (substr($hash, 0, 20) == pack('H*', sha1($password . substr($hash, 20))));
68
    }
69
70
    private function SHA1($crypted, $password) {
71
		$non_salted_sha1 = "{SHA}" . base64_encode(pack("H*", sha1($password)));
72
        return ($non_salted_sha1 == $crypted);
73
    }
74
75
    private function MD5($crypted, $password) {
76
        // thanks to http://blog.ethlo.com/2013/02/01/using-php-and-existing-htpasswd-file-for-authentication.html
77
        $passParts = explode('$', $crypted);
78
        $salt      = $passParts[2];
79
        $hashed    = self::cryptApr1Md5($password, $salt);
80
        return $hashed == $crypted;
81
    }
82
83
    private function bcrypt($crypted, $password) {
84
        return password_verify($password, $crypted);
85
    }
86
87
    private function cryptApr1Md5($plainpasswd, $salt) {
88
        $len  = strlen($plainpasswd);
89
        $text = $plainpasswd.'$apr1$'.$salt;
90
        $bin  = pack("H32", md5($plainpasswd.$salt.$plainpasswd));
91
        for ($i = $len; $i > 0; $i -= 16) { 
92
            $text .= substr($bin, 0, min(16, $i));
93
        }
94
        for ($i = $len; $i > 0; $i >>= 1) {
95
            $text .= ($i & 1) ? chr(0) : $plainpasswd[0];
96
        }
97
        $bin = pack("H32", md5($text));
98
        for ($i = 0; $i < 1000; $i++) {
99
            $new = ($i & 1) ? $plainpasswd : $bin;
100
            if ($i % 3) {
101
                $new .= $salt;
102
            }
103
            if ($i % 7) {
104
                $new .= $plainpasswd;
105
            }
106
            $new .= ($i & 1) ? $bin : $plainpasswd;
107
            $bin = pack("H32", md5($new));
108
        }
109
        $tmp = '';
110
        for ($i = 0; $i < 5; $i++) {
111
            $k = $i + 6;
112
            $j = $i + 12;
113
            if ($j == 16) {
114
                $j = 5;
115
            }
116
            $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
117
        }
118
        $tmp = chr(0).chr(0).$bin[11].$tmp;
119
        $tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
120
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
121
        "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
122
        return "$"."apr1"."$".$salt."$".$tmp;
123
    }
124
}
125