Project

General

Profile

wb-2_10_x / branches / main / framework / PasswordHash.php @ 10

1
<?php
2
/**
3
 * @category     Core
4
 * @package      Core_security
5
 * @author       Werner v.d.Decken
6
 * @copyright    ISTeasy-project(http://isteasy.de/)
7
 * @license      Creative Commons BY-SA 3.0 http://creativecommons.org/licenses/by-sa/3.0/
8
 * @version      $Id: PasswordHash.php 2 2017-07-02 15:14:29Z Manuela $
9
 * @filesource   $HeadURL: svn://isteam.dynxs.de/wb/2.10.x/branches/main/framework/PasswordHash.php $
10
 * @since        Datei vorhanden seit Release 2.8.2
11
 * @lastmodified $Date: 2017-07-02 17:14:29 +0200 (Sun, 02 Jul 2017) $
12
 *
13
 * this class works with salted md5-hashes with several rounds. 
14
 * For backward compatibility it can compare normal md5-hashes also.
15
 * Minimum requirements: PHP 5.2.2 or higher
16
 *
17
 * *****************************************************************************
18
 * This class is based on the Portable PHP password hashing framework.
19
 * Version 0.3 / genuine. Written by Solar Designer <solar at openwall.com>
20
 * in 2004-2006 and placed in the public domain. Revised in subsequent years,
21
 * still public domain. There's absolutely no warranty.
22
 * The homepage URL for this framework is: http://www.openwall.com/phpass/
23
 * *****************************************************************************
24
 */
25
class PasswordHash {
26

    
27
    const SECURITY_WEAK      = 6;
28
    const SECURITY_MEDIUM    = 8;
29
    const SECURITY_NORMAL    = 10;
30
    const SECURITY_STRONG    = 12;
31
    const SECURITY_STRONGER  = 16;
32

    
33
    private $_itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
34
    private $_iterationCountLog2 = 8;
35
    private $_portableHashes = true;
36
    private $_randomState = '';
37
    
38
    /**
39
     * @param int $iterationCountLog2 number of iterations as exponent of 2
40
     * @param bool $portableHashes TRUE = use MD5 only | FALSE = automatic
41
     */
42
    public function __construct($iterationCountLog2, $portableHashes = true)
43
    {
44

    
45
        if ($iterationCountLog2 < 4 || $iterationCountLog2 > 31) {
46
            $iterationCountLog2 = 8;
47
        }
48
        $this->_iterationCountLog2 = $iterationCountLog2;
49
        $this->_portableHashes = $portableHashes;
50
        $this->_randomState = microtime();
51
        if (function_exists('getmypid')) {
52
            $this->_randomState .= getmypid();
53
        }
54
    }
55

    
56

    
57
    private function _getRandomBytes($count)
58
    {
59
        $output = '';
60
        if (is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) {
61
            $output = fread($fh, $count);
62
            fclose($fh);
63
        }
64
        if (strlen($output) < $count) {
65
            $output = '';
66
            for ($i = 0; $i < $count; $i += 16) {
67
                $this->_randomState = md5(microtime() . $this->_randomState);
68
                $output .= pack('H*', md5($this->_randomState));
69
            }
70
            $output = substr($output, 0, $count);
71
        }
72
        return $output;
73
    }
74

    
75
    private function _Encode64($input, $count)
76
    {
77
        $output = '';
78
        $i = 0;
79
        do {
80
            $value = ord($input[$i++]);
81
            $output .= $this->_itoa64[$value & 0x3f];
82
            if ($i < $count) {
83
                $value |= ord($input[$i]) << 8;
84
            }
85
            $output .= $this->_itoa64[($value >> 6) & 0x3f];
86
            if ($i++ >= $count) { break; }
87
            if ($i < $count) {
88
                $value |= ord($input[$i]) << 16;
89
            }
90
            $output .= $this->_itoa64[($value >> 12) & 0x3f];
91
            if ($i++ >= $count) { break; }
92
            $output .= $this->_itoa64[($value >> 18) & 0x3f];
93
        } while ($i < $count);
94
        return $output;
95
    }
96

    
97
    private function _GenSaltPrivate($input)
98
    {
99
        $output = '$P$';
100
        $output .= $this->_itoa64[min($this->_iterationCountLog2 + 5, 30)];
101
        $output .= $this->_Encode64($input, 6);
102
        return $output;
103
    }
104

    
105
    private function _CryptPrivate($password, $setting)
106
    {
107
        $output = '*0';
108
        if (substr($setting, 0, 2) == $output) {
109
            $output = '*1';
110
        }
111
        $id = substr($setting, 0, 3);
112
        # We use "$P$", phpBB3 uses "$H$" for the same thing
113
        if ($id != '$P$' && $id != '$H$') {
114
            return $output;
115
        }
116
        $count_log2 = strpos($this->_itoa64, $setting[3]);
117
        if ($count_log2 < 7 || $count_log2 > 30) {
118
            return $output;
119
        }
120
        $count = 1 << $count_log2;
121
        $salt = substr($setting, 4, 8);
122
        if (strlen($salt) != 8) {
123
            return $output;
124
        }
125
        # We're kind of forced to use MD5 here since it's the only
126
        # cryptographic primitive available in all versions of PHP
127
        # currently in use.  To implement our own low-level crypto
128
        # in PHP would result in much worse performance and
129
        # consequently in lower iteration counts and hashes that are
130
        # quicker to crack (by non-PHP code).
131
        $hash = md5($salt . $password, TRUE);
132
        do {
133
            $hash = md5($hash . $password, TRUE);
134
        } while (--$count);
135
        $output = substr($setting, 0, 12);
136
        $output .= $this->_Encode64($hash, 16);
137
        return $output;
138
    }
139

    
140
    /**
141
     * calculate the hash from a given password
142
     * @param string $password password as original string
143
     * @return string generated hash | '*' on error
144
     */
145
    public function HashPassword($password, $md5 = false)
146
    {
147
        if ($md5) { return(md5($password)); }
148
        $random = '';
149
        if (strlen($random) < 6) {
150
            $random = $this->_getRandomBytes(6);
151
        }
152
        $hash = $this->_CryptPrivate($password, $this->_GenSaltPrivate($random));
153
        if (strlen($hash) == 34) {
154
            return $hash;
155
        }
156
        # Returning '*' on error is safe here, but would _not_ be safe
157
        # in a crypt(3)-like function used _both_ for generating new
158
        # hashes and for validating passwords against existing hashes.
159
        return '*';
160
    }
161

    
162
    /**
163
     * encodes the password and compare it against the given hash
164
     * @param string $password clear password
165
     * @param string $stored_hash the hash to compare against
166
     * @return bool
167
     */
168
    public function CheckPassword($password, $stored_hash)
169
    {
170
    // compare against a normal, simple md5-hash
171
        if(preg_match('/^[0-9a-f]{32}$/i', $stored_hash)) {
172
            return md5($password) == $stored_hash;
173
        }
174
    // compare against a rounded, salted md5-hash
175
        $hash = $this->_CryptPrivate($password, $stored_hash);
176
        if ($hash[0] == '*') {
177
            $hash = crypt($password, $stored_hash);
178
        }
179
        return $hash == $stored_hash;
180
    }
181
    /**
182
     * generate a case sensitive mnemonic password including numbers and special chars
183
     * makes no use of lowercase 'l', uppercase 'I', 'O' or number '0'
184
     * @param int $length length of the generated password. default = 8
185
     * @return string
186
     */
187
    public static function NewPassword($length = self::SECURITY_MEDIUM)
188
    {
189
        $chars = array(
190
            array('b','c','d','f','g','h','j','k','l','m','n','p','r','s','t','v','w','x','y','z'),
191
            array('a','e','i','o','u'),
192
            array('!','-','@','_',':','.','+','%','/','*')
193
        );
194
        if($length < self::SECURITY_WEAK) { $length = self::SECURITY_WEAK; }
195
        $length = ceil($length / 2);
196
        $Password = array();
197
    // at first fill array alternating with vowels and consonants
198
        for($x = 0; $x < $length; $x++) {
199
            $char = $chars[0][rand(1000, 10000) % sizeof($chars[0])];
200
            $Password[] = $char == 'l' ? 'L' : $char;
201
            $Password[] = $chars[1][rand(1000, 10000) % sizeof($chars[1])];
202
        }
203
    // transform some random chars into uppercase
204
        $pos = ((rand(1000, 10000) % 3) + 1);
205
        while($pos < sizeof($Password)) {
206
            $Password[$pos] = ($Password[$pos] == 'i' || $Password[$pos] == 'o')
207
                              ? $Password[$pos] : strtoupper($Password[$pos]);
208
            $pos += ((rand(1000, 10000) % 3) + 1);
209
        }
210
    // insert some numeric chars, between 1 and 9
211
        $specialChars = array();
212
        $specialCharsCount = floor(sizeof($Password) / 4);
213
        while(sizeof($specialChars) < $specialCharsCount) {
214
            $key = (rand(1000, 10000) % sizeof($Password));
215
            if(!isset($specialChars[$key])) {
216
                $specialChars[$key] = (rand(1000, 10000) % 9) + 1;
217
            }
218
        }
219
    // insert some punctuation chars, but not leading or trailing
220
        $specialCharsCount += floor((sizeof($Password)-1) / 6);
221
        while(sizeof($specialChars) < $specialCharsCount) {
222
            $key = (rand(1000, 10000) % (sizeof($Password)-2))+1;
223
            if(!isset($specialChars[$key])) {
224
                $specialChars[$key] = $chars[2][(rand(1000, 10000) % sizeof($chars[2]))];
225
            }
226
        }
227
        foreach($specialChars as $key=>$val) {
228
            $Password[$key] = $val;
229
        }
230

    
231
        return implode($Password);
232
    }
233

    
234
} // end of class