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: 2070 $
26
 * @link         $HeadURL: svn://isteam.dynxs.de/wb-archiv/branches/2.8.x/wb/framework/Password.php $
27
 * @lastmodified $Date: 2014-01-03 02:21:42 +0100 (Fri, 03 Jan 2014) $
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
class Password
35
{
36

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

    
41
	const HASH_TYPE_PORTABLE  = true;  // use MD5 only
42
	const HASH_TYPE_AUTO      = false; // select highest available crypting methode (default)
43

    
44
	const PW_LENGTH_MIN       =   6;
45
	const PW_LENGTH_MAX       = 100;
46
	const PW_LENGTH_DEFAULT   =  10;
47

    
48
	const PW_USE_LOWERCHAR    = 0x0001; // use lower chars
49
	const PW_USE_UPPERCHAR    = 0x0002; // use upper chars
50
	const PW_USE_DIGITS       = 0x0004; // use numeric digits
51
	const PW_USE_SPECIAL      = 0x0008; // use special chars
52
	const PW_USE_ALL          = 0xFFFF; // use all possibilities
53

    
54
	/** holds the active singleton instance */
55
	private static $_oInstance     = null;
56

    
57
	private   $oPwHashClass        = null;
58
	protected $iIterationCountLog2 = self::CRYPT_LOOPS_DEFAULT;
59
	protected $bPortableHashes     = self::HASH_TYPE_AUTO;
60

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

    
261
} // end of class Password
262
// //////////////////////////////////////////////////////////////////////////////////// //
263
/**
264
 * PasswordException
265
 *
266
 * @category     WBCore
267
 * @package      WBCore_Security
268
 * @author       Werner v.d.Decken <wkl@isteam.de>
269
 * @copyright    Werner v.d.Decken <wkl@isteam.de>
270
 * @license      http://www.gnu.org/licenses/gpl.html   GPL License
271
 * @version      2.9.0
272
 * @revision     $Revision: 2070 $
273
 * @lastmodified $Date: 2014-01-03 02:21:42 +0100 (Fri, 03 Jan 2014) $
274
 */
275
class PasswordException extends AppException { }
(7-7/40)