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 
4 require_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 
31  private $is_security_flaw_ignored = false;
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  {
81  return (bool)$this->backward_compatibility;
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 
120  public function setClientSalt($client_salt)
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  {
250  return ilUtil::getDataDir() . '/' . self::SALT_STORAGE_FILENAME;
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 }
setBackwardCompatibility($backward_compatibility)
Set the backward compatibility $2a$ instead of $2y$ for PHP 5.3.7+.
encodePassword($raw, $salt)
{Encodes the raw password.The password to encode The salt string The encoded password} ...
$result
requiresSalt()
{Returns whether or not the encoder requires a salt.boolean}
Class for user password exception handling in ILIAS.
requiresReencoding($encoded)
{Returns whether or not the a encoded password needs to be re-encoded.string boolean} ...
static getBytes($length)
Generate random bytes using OpenSSL or Mcrypt and mt_rand() as fallback.
setIsSecurityFlawIgnored($is_security_flaw_ignored)
Create styles array
The data for the language used.
static getDataDir()
get data directory (outside webspace)
getName()
{Returns a unique name/id of the concrete password encoder.string}
encode($raw, $user_secret)
Generates a bcrypt encoded string.
isPasswordValid($encoded, $raw, $salt)
{Checks a raw password against an encoded password.The raw password has to be injected into the encod...
check($encoded, $raw, $salt)
Verifies a bcrypt encoded string.
isPasswordTooLong($password)
Checks if the password is too long.