ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
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/encoders/class.ilBcryptPhpPasswordEncoder.php';
5
12{
16 const MIN_SALT_SIZE = 16;
17
21 const SALT_STORAGE_FILENAME = 'pwsalt.txt';
22
26 private $client_salt = null;
27
32
36 private $backward_compatibility = false;
37
42 public function __construct(array $config = array())
43 {
44 if(!empty($config))
45 {
46 foreach($config as $key => $value)
47 {
48 switch(strtolower($key))
49 {
50 case 'ignore_security_flaw':
51 $this->setIsSecurityFlawIgnored($value);
52 break;
53 }
54 }
55 }
56
57 parent::__construct($config);
58 }
59
63 protected function init()
64 {
65 $this->readClientSalt();
66 }
67
71 protected function isBcryptSupported()
72 {
73 return PHP_VERSION_ID >= 50307;
74 }
75
80 {
82 }
83
89 {
90 $this->backward_compatibility = (bool)$backward_compatibility;
91 }
92
96 public function isSecurityFlawIgnored()
97 {
99 }
100
105 {
106 $this->is_security_flaw_ignored = (bool)$is_security_flaw_ignored;
107 }
108
112 public function getClientSalt()
113 {
114 return $this->client_salt;
115 }
116
121 {
122 $this->client_salt = $client_salt;
123 }
124
129 public function encodePassword($raw, $salt)
130 {
131 if(!$this->getClientSalt())
132 {
133 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
134 throw new ilPasswordException('Missing client salt.');
135 }
136
137 if($this->isPasswordTooLong($raw))
138 {
139 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
140 throw new ilPasswordException('Invalid password.');
141 }
142
143 return $this->encode($raw, $salt);
144 }
145
149 public function isPasswordValid($encoded, $raw, $salt)
150 {
151 if(!$this->getClientSalt())
152 {
153 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
154 throw new ilPasswordException('Missing client salt.');
155 }
156
157 return !$this->isPasswordTooLong($raw) && $this->check($encoded, $raw, $salt);
158 }
159
163 public function getName()
164 {
165 return 'bcrypt';
166 }
167
171 public function requiresSalt()
172 {
173 return true;
174 }
175
176
177
181 public function requiresReencoding($encoded)
182 {
183 return false;
184 }
185
193 protected function encode($raw, $user_secret)
194 {
195 $client_secret = $this->getClientSalt();
196 $hashed_password = hash_hmac('whirlpool', str_pad($raw, strlen($raw) * 4, sha1($user_secret), STR_PAD_BOTH), $client_secret, true);
197 $salt = substr(str_shuffle(str_repeat('./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 22)), 0, 22);
198
203 if($this->isBcryptSupported() && !$this->isBackwardCompatibilityEnabled())
204 {
205 $prefix = '$2y$';
206 }
207 else
208 {
209 $prefix = '$2a$';
210 // check if the password contains 8-bit character
211 if(!$this->isSecurityFlawIgnored() && preg_match('/[\x80-\xFF]/', $raw))
212 {
213 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
214 throw new ilPasswordException(
215 'The bcrypt implementation used by PHP can contain a security flaw ' .
216 'using passwords with 8-bit characters. ' .
217 'We suggest to upgrade to PHP 5.3.7+ or use passwords with only 7-bit characters.'
218 );
219 }
220 }
221
222 $salted_password = crypt($hashed_password, $prefix . $this->getCosts() . '$' . $salt);
223 if(strlen($salted_password) <= 13)
224 {
225 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
226 throw new ilPasswordException('Error during the bcrypt generation');
227 }
228
229 return $salted_password;
230 }
231
239 protected function check($encoded, $raw, $salt)
240 {
241 $hashed_password = hash_hmac('whirlpool', str_pad($raw, strlen($raw) * 4, sha1($salt), STR_PAD_BOTH), $this->getClientSalt(), true);
242 return crypt($hashed_password, substr($encoded, 0, 30)) == $encoded;
243 }
244
248 public function getClientSaltLocation()
249 {
251 }
252
256 private function readClientSalt()
257 {
258 if(is_file($this->getClientSaltLocation()) && is_readable($this->getClientSaltLocation()))
259 {
260 $contents = file_get_contents($this->getClientSaltLocation());
261 if(strlen(trim($contents)))
262 {
263 $this->setClientSalt($contents);
264 }
265 }
266 else
267 {
268 $this->generateClientSalt();
269 $this->storeClientSalt();
270 }
271 }
272
276 private function generateClientSalt()
277 {
278 require_once 'Services/Password/classes/class.ilPasswordUtils.php';
279 $this->setClientSalt(
280 substr(str_replace('+', '.', base64_encode(ilPasswordUtils::getBytes(self::MIN_SALT_SIZE))), 0, 22)
281 );
282 }
283
287 private function storeClientSalt()
288 {
289 $result = @file_put_contents($this->getClientSaltLocation(), $this->getClientSalt());
290 if(!$result)
291 {
292 require_once 'Services/Password/exceptions/class.ilPasswordException.php';
293 throw new ilPasswordException(sprintf("Could not store the client salt in: %s. Please contact an administrator.", $this->getClientSaltLocation()));
294 }
295 }
296}
sprintf('%.4f', $callTime)
$result
An exception for terminatinating execution or to throw for unit testing.
isPasswordTooLong($password)
Checks if the password is too long.
setIsSecurityFlawIgnored($is_security_flaw_ignored)
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}}
requiresReencoding($encoded)
{{{Returns whether or not the a encoded password needs to be re-encoded.boolean}}}
isPasswordValid($encoded, $raw, $salt)
{{Checks a raw password against an encoded password.The raw password has to be injected into the enco...
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)