ILIAS  release_5-1 Revision 5.0.0-5477-g43f3e3fab5f
class.ilBcryptPasswordEncoder.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2014 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4require_once 'Services/Password/classes/class.ilBasePasswordEncoder.php';
5
12{
16 const MIN_SALT_SIZE = 16;
17
21 const SALT_STORAGE_FILENAME = 'pwsalt.txt';
22
26 protected $client_salt = null;
27
31 protected $costs = '08';
32
36 protected $is_security_flaw_ignored = false;
37
41 protected $backward_compatibility = false;
42
47 public function __construct(array $config = array())
48 {
49 if(!empty($config))
50 {
51 foreach($config as $key => $value)
52 {
53 switch(strtolower($key))
54 {
55 case 'cost':
56 $this->setCosts($value);
57 break;
58
59 case 'ignore_security_flaw':
60 $this->setIsSecurityFlawIgnored($value);
61 break;
62 }
63 }
64 }
65
66 $this->init();
67 }
68
72 protected function init()
73 {
74 $this->readClientSalt();
75 }
76
80 protected function isBcryptSupported()
81 {
82 return PHP_VERSION_ID >= 50307;
83 }
84
89 {
91 }
92
98 {
99 $this->backward_compatibility = (bool)$backward_compatibility;
100 }
101
105 public function isSecurityFlawIgnored()
106 {
108 }
109
114 {
115 $this->is_security_flaw_ignored = (bool)$is_security_flaw_ignored;
116 }
117
121 public function getClientSalt()
122 {
123 return $this->client_salt;
124 }
125
130 {
131 $this->client_salt = $client_salt;
132 }
133
137 public function getCosts()
138 {
139 return $this->costs;
140 }
141
146 public function setCosts($costs)
147 {
148 if(!empty($costs))
149 {
150 $costs = (int)$costs;
151 if($costs < 4 || $costs > 31)
152 {
153 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
154 throw new ilPasswordException('The costs parameter of bcrypt must be in range 04-31');
155 }
156 $this->costs = sprintf('%1$02d', $costs);
157 }
158 }
159
164 public function encodePassword($raw, $salt)
165 {
166 if(!$this->getClientSalt())
167 {
168 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
169 throw new ilPasswordException('Missing client salt.');
170 }
171
172 if($this->isPasswordTooLong($raw))
173 {
174 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
175 throw new ilPasswordException('Invalid password.');
176 }
177
178 return $this->encode($raw, $salt);
179 }
180
184 public function isPasswordValid($encoded, $raw, $salt)
185 {
186 if(!$this->getClientSalt())
187 {
188 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
189 throw new ilPasswordException('Missing client salt.');
190 }
191
192 return !$this->isPasswordTooLong($raw) && $this->check($encoded, $raw, $salt);
193 }
194
198 public function getName()
199 {
200 return 'bcrypt';
201 }
202
206 public function requiresSalt()
207 {
208 return true;
209 }
210
218 protected function encode($raw, $user_secret)
219 {
220 $client_secret = $this->getClientSalt();
221 $hashed_password = hash_hmac('whirlpool', str_pad($raw, strlen($raw) * 4, sha1($user_secret), STR_PAD_BOTH), $client_secret, true);
222 $salt = substr(str_shuffle(str_repeat('./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 22)), 0, 22);
223
228 if($this->isBcryptSupported() && !$this->isBackwardCompatibilityEnabled())
229 {
230 $prefix = '$2y$';
231 }
232 else
233 {
234 $prefix = '$2a$';
235 // check if the password contains 8-bit character
236 if(!$this->isSecurityFlawIgnored() && preg_match('/[\x80-\xFF]/', $raw))
237 {
238 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
239 throw new ilPasswordException(
240 'The bcrypt implementation used by PHP can contain a security flaw ' .
241 'using passwords with 8-bit characters. ' .
242 'We suggest to upgrade to PHP 5.3.7+ or use passwords with only 7-bit characters.'
243 );
244 }
245 }
246
247 $salted_password = crypt($hashed_password, $prefix . $this->getCosts() . '$' . $salt);
248 if(strlen($salted_password) <= 13)
249 {
250 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
251 throw new ilPasswordException('Error during the bcrypt generation');
252 }
253
254 return $salted_password;
255 }
256
264 protected function check($encoded, $raw, $salt)
265 {
266 $hashed_password = hash_hmac('whirlpool', str_pad($raw, strlen($raw) * 4, sha1($salt), STR_PAD_BOTH), $this->getClientSalt(), true);
267 return crypt($hashed_password, substr($encoded, 0, 30)) == $encoded;
268 }
269
273 public function getClientSaltLocation()
274 {
276 }
277
281 private function readClientSalt()
282 {
283 if(is_file($this->getClientSaltLocation()) && is_readable($this->getClientSaltLocation()))
284 {
285 $contents = file_get_contents($this->getClientSaltLocation());
286 if(strlen(trim($contents)))
287 {
288 $this->setClientSalt($contents);
289 }
290 }
291 else
292 {
293 $this->generateClientSalt();
294 $this->storeClientSalt();
295 }
296 }
297
301 private function generateClientSalt()
302 {
303 require_once 'Services/Password/classes/class.ilPasswordUtils.php';
304 $this->setClientSalt(
305 substr(str_replace('+', '.', base64_encode(ilPasswordUtils::getBytes(self::MIN_SALT_SIZE))), 0, 22)
306 );
307 }
308
312 private function storeClientSalt()
313 {
314 $result = @file_put_contents($this->getClientSaltLocation(), $this->getClientSalt());
315 if(!$result)
316 {
317 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
318 throw new ilPasswordException(sprintf("Could not store the client salt in: %s. Please contact an administrator.", $this->getClientSaltLocation()));
319 }
320 }
321}
$result
isPasswordTooLong($password)
Checks if the password is too long.
setIsSecurityFlawIgnored($is_security_flaw_ignored)
getName()
{Returns a unique name/id of the concrete password encoder.string}
setBackwardCompatibility($backward_compatibility)
Set the backward compatibility $2a$ instead of $2y$ for PHP 5.3.7+.
encodePassword($raw, $salt)
{Encodes the raw password.string The encoded password}
isPasswordValid($encoded, $raw, $salt)
{Checks a raw password against an encoded password.The raw password has to be injected into the encod...
requiresSalt()
{Returns whether or not the encoder requires a salt.boolean}
check($encoded, $raw, $salt)
Verifies a bcrypt encoded string.
encode($raw, $user_secret)
Generates a bcrypt encoded string.
Class for user password exception handling in ILIAS.
static getBytes($length)
Generate random bytes using OpenSSL or Mcrypt and mt_rand() as fallback.
static getDataDir()
get data directory (outside webspace)