Project

General

Profile

1
<?php
2
/**
3
 *  Copyright (C) 2012 Werner v.d. Decken <wkl@isteam.de>
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
/**
19
 * @category     WBCore
20
 * @package      WBCore_Security
21
 * @author       Werner v.d. Decken <wkl@isteam.de>
22
 * @copyright    Werner v.d. Decken <wkl@isteam.de>
23
 * @license      http://www.gnu.org/licenses/gpl.html   GPL License
24
 * @version      1.0.3
25
 * @revision     $Revision: 1932 $
26
 * @link         $HeadURL: svn://isteam.dynxs.de/wb-archiv/branches/2.8.x/wb/framework/Password.php $
27
 * @lastmodified $Date: 2013-07-13 10:15:27 +0200 (Sat, 13 Jul 2013) $
28
 * @since        Datei vorhanden seit Release 1.2.0
29
 * @description  This class is interfacing the Portable PHP password hashing framework.<br />
30
 *               Version 0.4 / ISTeam Rev. 0.1<br />
31
 *               ISTeam changes: added SHA-256, SHA-512 (2012/10/27 Werner v.d. Decken)
32
 */
33

    
34
if(!class_exists('PasswordHash')) {
35
	include(dirname(dirname(__FILE__)).'/include/phpass/PasswordHash.php');
36
}
37

    
38
class Password
39
{
40

    
41
	const CRYPT_LOOPS_MIN     =  6;  // minimum numbers of loops is 2^6 (64) very quick but unsecure
42
	const CRYPT_LOOPS_MAX     = 31;  // maximum numbers of loops is 2^31 (2,147,483,648) extremely slow
43
	const CRYPT_LOOPS_DEFAULT = 12;  // default numbers of loopf is 2^12 (4096) a good average
44

    
45
	const HASH_TYPE_PORTABLE  = true;  // use MD5 only
46
	const HASH_TYPE_AUTO      = false; // select highest available crypting methode (default)
47

    
48
	const PW_LENGTH_MIN       =   6;
49
	const PW_LENGTH_MAX       = 100;
50
	const PW_LENGTH_DEFAULT   =  10;
51

    
52
	const PW_USE_LOWERCHAR    = 0x0001; // use lower chars
53
	const PW_USE_UPPERCHAR    = 0x0002; // use upper chars
54
	const PW_USE_DIGITS       = 0x0004; // use numeric digits
55
	const PW_USE_SPECIAL      = 0x0008; // use special chars
56
	const PW_USE_ALL          = 0xFFFF; // use all possibilities
57

    
58
	/** holds the active singleton instance */
59
	private static $_oInstance     = null;
60

    
61
	private   $oPwHashClass        = null;
62
	protected $iIterationCountLog2 = self::CRYPT_LOOPS_DEFAULT;
63
	protected $bPortableHashes     = self::HASH_TYPE_AUTO;
64

    
65
/**
66
 * constructor
67
 */
68
	protected function __construct()
69
	{
70
	}
71
/**
72
 * dissable cloning
73
 */
74
	private function __clone() {
75
		;
76
	}
77
/**
78
 * get current instance or create new one
79
 * @return Password
80
 */
81
	public static function getInstance($oPwHash = null)
82
	{
83
		if( is_null(self::$_oInstance) ) {
84
			if(is_object($oPwHash) && ($oPwHash instanceof PasswordHashInterface) ) {
85
				$c = __CLASS__;
86
				self::$_oInstance = new $c;
87
				self::$_oInstance->oPwHashClass = $oPwHash;
88
				self::$_oInstance->setIteration(self::CRYPT_LOOPS_DEFAULT);
89
				self::$_oInstance->setHashType(self::HASH_TYPE_AUTO);
90
			}else {
91
				throw new PasswordException('hashing class is not an object or does not implement PasswordHashInterface');
92
			}
93
		}
94
		return self::$_oInstance;
95
	}
96
/**
97
 * set the number of iterations
98
 * @param int $iIterationCountLog2 number of iterations defined as the exponent to basic 2
99
 */
100
	public function setIteration($iIterationCountLog2 = self::CRYPT_LOOPS_DEFAULT)
101
	{
102
		$this->$iIterationCountLog2 = min(max($iIterationCountLog2, self::CRYPT_LOOPS_MIN), self::CRYPT_LOOPS_MAX);
103
		$this->oPwHashClass->setParams($this->iIterationCountLog2, $this->bPortableHashes);
104
	}
105
/**
106
 * set type of hash generation
107
 * @param bool $bPortableHashes
108
 * @description HASH_TYPE_AUTO will choose the higest available algorithm to create a hash (default)<br />
109
 *              Attention: it's possible that high level generated hashes from PHP>=5.3 are not validable under PHP<5.3!!<br />
110
 *              HASH_TYPE_PORTABLE choose MD5 hashing with salt and n iterations
111
 */
112
	public function setHashType($bPortableHashes = self::HASH_TYPE_AUTO)
113
	{
114
		if(version_compare('5.3', PHP_VERSION, '<')) {
115
			$this->bPortableHashes = self::HASH_TYPE_PORTABLE;
116
		}else {
117
			$this->bPortableHashes = (boolean)$bPortableHashes;
118
		}
119
		$this->oPwHashClass->setParams($this->iIterationCountLog2, $this->bPortableHashes);
120
	}
121
/**
122
 * make hash from password
123
 * @param string password to hash
124
 * @return string generated hash. Null if failed.
125
 */
126
	public function makeHash($sPassword)
127
	{
128
		if(!is_object($this->oPwHashClass)) {
129
			throw new PasswordException('Missing Object to calculate hashes');
130
		}
131
		$sNewHash = $this->oPwHashClass->HashPassword($sPassword);
132
		return ($sNewHash == '*') ? null : $sNewHash;
133
	}
134
/**
135
 * @param string Password to test against given Hash
136
 * @param string existing stored hash
137
 * @return bool true if PW matches the stored hash
138
 */
139
	public function checkIt($sPassword, $sStoredHash)
140
	{
141
		if(!is_object($this->oPwHashClass)) {
142
			throw new PasswordException('Missing Object to calculate hashes');
143
		}
144
		// compatibility layer for deprecated, simple and old MD5 hashes
145
		if(preg_match('/^[0-9a-f]{32}$/si', $sStoredHash)) {
146
			return (md5($sPassword) === $sStoredHash);
147
		}
148
		return $this->oPwHashClass->CheckPassword($sPassword, $sStoredHash);
149
	}
150
/**
151
 * Check password for forbidden characters
152
 * @param string password to test
153
 * @return bool
154
 */
155
	public static function isValid($sPassword)
156
	{
157
/** @todo extend blacklist with additional utf8 codes */
158
		$sBlackList = '\"\'\,\;\<\>\?\\\{\|\}\~ '
159
		            . '\x00-\x20\x22\x27\x2c\x3b\x3c\x3e\x3f\x5c\x7b-\x7f\xff';
160
		$bRetval = !preg_match('/['.$sBlackList.']/si', $sPassword);
161
		return $bRetval;
162
	}
163
/**
164
 * generate a case sensitive mnemonic password including numbers and special chars
165
 * makes no use of confusing characters like 'O' and '0' and so on.
166
 * @param int length of the generated password. default = PW_LENGTH_DEFAULT
167
 * @param int defines which elemets are used to generate a password. Default = PW_USE_ALL
168
 * @return string
169
 */
170
	public static function createNew($iLength = self::PW_LENGTH_DEFAULT, $iElements = self::PW_USE_ALL)
171
	{
172
		$aChars = array(
173
			array('b','c','d','f','g','h','j','k','m','n','p','q','r','s','t','v','w','x','y','z'),
174
			array('B','C','D','F','G','H','J','K','M','N','P','Q','R','S','T','V','W','X','Y','Z'),
175
			array('a','e','i','o','u'),
176
			array('A','E','U'),
177
			array('!','-','@','_',':','.','+','%','/','*','=')
178
		);
179
		$iElements = ($iElements & self::PW_USE_ALL) == 0 ? self::PW_USE_ALL : $iElements;
180
		if(($iLength < self::PW_LENGTH_MIN) || ($iLength > self::PW_LENGTH_MAX)) {
181
			$iLength = self::PW_LENGTH_DEFAULT;
182
		}
183
	// at first create random arrays of lowerchars and uperchars
184
	// alternating between vowels and consonants
185
		$aUpperCase = array();
186
		$aLowerCase = array();
187
		for($x = 0; $x < ceil($iLength / 2); $x++) {
188
			// consonants
189
			$i1 = rand(1000, 10000) % sizeof($aChars[0]);
190
			$aLowerCase[] = $aChars[0][$i1];
191
			$i2 = rand(1000, 10000) % sizeof($aChars[1]);
192
			$aUpperCase[] = $aChars[1][$i2];
193
			// vowels
194
			$i3 = rand(1000, 10000) % sizeof($aChars[2]);
195
			$aLowerCase[] = $aChars[2][$i3];
196
			$i4 = rand(1000, 10000) % sizeof($aChars[3]);
197
			$aUpperCase[] = $aChars[3][$i4];
198
		}
199
	// create random arrays of numeric digits 2-9 and  special chars
200
		$aDigits       = array();
201
		$aSpecialChars = array();
202
		for($x = 0; $x < $iLength; $x++) {
203
			$aDigits[] = (rand(1000, 10000) % 8) + 2;
204
			$aSpecialChars[] = $aChars[4][rand(1000, 10000) % sizeof($aChars[4])];
205
		}
206
		// take string or merge chars depending from $iElements
207
		$aPassword = array();
208
		$bMerge = false;
209
		if($iElements & self::PW_USE_LOWERCHAR) {
210
			$aPassword = $aLowerCase;
211
			$bMerge = true;
212
		}
213
		if($iElements & self::PW_USE_UPPERCHAR) {
214
			if($bMerge) {
215
				$iNumberOfUpperChars = rand(1000, 10000) % ($iLength);
216
				$aPassword = self::_mergeIntoPassword($aPassword, $aUpperCase, $iNumberOfUpperChars);
217
			}else {
218
				$aPassword = $aUpperCase;
219
				$bMerge = true;
220
			}
221
		}
222
		if($iElements & self::PW_USE_DIGITS) {
223
			if($bMerge) {
224
				$x = (rand(1000, 10000) % ceil($iLength / 2.5));
225
				$iNumberOfDigits = $x ? $x : 1;
226
				$aPassword = self::_mergeIntoPassword($aPassword, $aDigits, $iNumberOfDigits);
227
			}else {
228
				$aPassword = $aDigits;
229
				$bMerge = true;
230
			}
231
		}
232
		if($iElements & self::PW_USE_SPECIAL) {
233
			if($bMerge) {
234
				$x = rand(1000, 10000) % ceil($iLength / 5);
235
				$iNumberOfSpecialChars = $x ? $x : 1;
236
				$aPassword = self::_mergeIntoPassword($aPassword, $aSpecialChars, $iNumberOfSpecialChars);
237
			}else {
238
				$aPassword = $aSpecialChars;
239
				$bMerge = true;
240
			}
241
		}
242
		$sPassword = implode('', array_slice($aPassword, 0, $iLength));
243
		return $sPassword;
244
	}
245
/**
246
 * merges $iCount chars from $aInsert randomly into $aPassword
247
 * @param array $aPassword
248
 * @param array $aInsert
249
 * @param integer $iCount
250
 * @return array
251
 */
252
	private static function _mergeIntoPassword($aPassword, $aInsert, $iCount)
253
	{
254
		$aListOfIndexes = array();
255
		while(sizeof($aListOfIndexes) < $iCount) {
256
			$x = rand(1000, 10000) % sizeof($aInsert);
257
			$aListOfIndexes[$x] = $x;
258
		}
259
		foreach($aListOfIndexes as $x) {
260
			$aPassword[$x] = $aInsert[$x];
261
		}
262
		return $aPassword;
263
	}
264

    
265
} // end of class Password
266
// //////////////////////////////////////////////////////////////////////////////////// //
267
/**
268
 * PasswordException
269
 *
270
 * @category     WBCore
271
 * @package      WBCore_Security
272
 * @author       Werner v.d.Decken <wkl@isteam.de>
273
 * @copyright    Werner v.d.Decken <wkl@isteam.de>
274
 * @license      http://www.gnu.org/licenses/gpl.html   GPL License
275
 * @version      2.9.0
276
 * @revision     $Revision: 1932 $
277
 * @lastmodified $Date: 2013-07-13 10:15:27 +0200 (Sat, 13 Jul 2013) $
278
 */
279
class PasswordException extends AppException { }
(4-4/33)