ILIAS  release_7 Revision v7.30-3-g800a261c036
class.ilBcryptPasswordEncoder.php
Go to the documentation of this file.
1 <?php declare(strict_types=1);
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 
13 {
15  const MIN_SALT_SIZE = 16;
16 
18  const SALT_STORAGE_FILENAME = 'pwsalt.txt';
19 
21  private $client_salt = null;
22 
24  private $is_security_flaw_ignored = false;
25 
27  private $backward_compatibility = false;
28 
30  private $data_directory = '';
31 
36  public function __construct(array $config = [])
37  {
38  if (!empty($config)) {
39  foreach ($config as $key => $value) {
40  switch (strtolower($key)) {
41  case 'ignore_security_flaw':
42  $this->setIsSecurityFlawIgnored($value);
43  break;
44 
45  case 'data_directory':
46  $this->setDataDirectory($value);
47  break;
48  }
49  }
50  }
51 
53  }
54 
58  protected function init() : void
59  {
60  $this->readClientSalt();
61  }
62 
66  protected function isBcryptSupported() : bool
67  {
68  return PHP_VERSION_ID >= 50307;
69  }
70 
74  public function getDataDirectory() : string
75  {
76  return $this->data_directory;
77  }
78 
82  public function setDataDirectory(string $data_directory) : void
83  {
84  $this->data_directory = $data_directory;
85  }
86 
90  public function isBackwardCompatibilityEnabled() : bool
91  {
92  return (bool) $this->backward_compatibility;
93  }
94 
100  {
101  $this->backward_compatibility = (bool) $backward_compatibility;
102  }
103 
107  public function isSecurityFlawIgnored() : bool
108  {
109  return (bool) $this->is_security_flaw_ignored;
110  }
111 
116  {
117  $this->is_security_flaw_ignored = (bool) $is_security_flaw_ignored;
118  }
119 
123  public function getClientSalt() : ?string
124  {
125  return $this->client_salt;
126  }
127 
131  public function setClientSalt(?string $client_salt)
132  {
133  $this->client_salt = $client_salt;
134  }
135 
140  public function encodePassword(string $raw, string $salt) : string
141  {
142  if (!$this->getClientSalt()) {
143  throw new ilPasswordException('Missing client salt.');
144  }
145 
146  if ($this->isPasswordTooLong($raw)) {
147  throw new ilPasswordException('Invalid password.');
148  }
149 
150  return $this->encode($raw, $salt);
151  }
152 
157  public function isPasswordValid(string $encoded, string $raw, string $salt) : bool
158  {
159  if (!$this->getClientSalt()) {
160  throw new ilPasswordException('Missing client salt.');
161  }
162 
163  return !$this->isPasswordTooLong($raw) && $this->check($encoded, $raw, $salt);
164  }
165 
169  public function getName() : string
170  {
171  return 'bcrypt';
172  }
173 
177  public function requiresSalt() : bool
178  {
179  return true;
180  }
181 
185  public function requiresReencoding(string $encoded) : bool
186  {
187  return false;
188  }
189 
197  protected function encode(string $raw, string $userSecret) : string
198  {
199  $clientSecret = $this->getClientSalt();
200  $hashedPassword = hash_hmac(
201  'whirlpool',
202  str_pad($raw, strlen($raw) * 4, sha1($userSecret), STR_PAD_BOTH),
203  $clientSecret,
204  true
205  );
206  $salt = substr(
207  str_shuffle(str_repeat('./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 22)),
208  0,
209  22
210  );
211 
216  if ($this->isBcryptSupported() && !$this->isBackwardCompatibilityEnabled()) {
217  $prefix = '$2y$';
218  } else {
219  $prefix = '$2a$';
220  // check if the password contains 8-bit character
221  if (!$this->isSecurityFlawIgnored() && preg_match('/[\x80-\xFF]/', $raw)) {
222  throw new ilPasswordException(
223  'The bcrypt implementation used by PHP can contain a security flaw ' .
224  'using passwords with 8-bit characters. ' .
225  'We suggest to upgrade to PHP 5.3.7+ or use passwords with only 7-bit characters.'
226  );
227  }
228  }
229 
230  $saltedPassword = crypt($hashedPassword, $prefix . $this->getCosts() . '$' . $salt);
231  if (strlen($saltedPassword) <= 13) {
232  throw new ilPasswordException('Error during the bcrypt generation');
233  }
234 
235  return $saltedPassword;
236  }
237 
245  protected function check(string $encoded, string $raw, string $salt) : bool
246  {
247  $hashedPassword = hash_hmac(
248  'whirlpool',
249  str_pad($raw, strlen($raw) * 4, sha1($salt), STR_PAD_BOTH),
250  $this->getClientSalt(),
251  true
252  );
253 
254  return $this->comparePasswords($encoded, crypt($hashedPassword, substr($encoded, 0, 30)));
255  }
256 
260  public function getClientSaltLocation() : string
261  {
262  return $this->getDataDirectory() . '/' . self::SALT_STORAGE_FILENAME;
263  }
264 
268  private function readClientSalt() : void
269  {
270  if (is_file($this->getClientSaltLocation()) && is_readable($this->getClientSaltLocation())) {
271  $contents = file_get_contents($this->getClientSaltLocation());
272  if (strlen(trim($contents))) {
273  $this->setClientSalt($contents);
274  }
275  } else {
276  $this->generateClientSalt();
277  $this->storeClientSalt();
278  }
279  }
280 
284  private function generateClientSalt() : void
285  {
286  $this->setClientSalt(
287  substr(str_replace('+', '.', base64_encode(ilPasswordUtils::getBytes(self::MIN_SALT_SIZE))), 0, 22)
288  );
289  }
290 
294  private function storeClientSalt() : void
295  {
296  $result = @file_put_contents($this->getClientSaltLocation(), $this->getClientSalt());
297  if (!$result) {
298  throw new ilPasswordException(sprintf(
299  "Could not store the client salt in: %s. Please contact an administrator.",
300  $this->getClientSaltLocation()
301  ));
302  }
303  }
304 }
check(string $encoded, string $raw, string $salt)
Verifies a bcrypt encoded string.
setBackwardCompatibility(bool $backward_compatibility)
Set the backward compatibility $2a$ instead of $2y$ for PHP 5.3.7+.
$result
if(!array_key_exists('PATH_INFO', $_SERVER)) $config
Definition: metadata.php:68
comparePasswords(string $knownString, string $userString)
Compares two passwords.
setIsSecurityFlawIgnored(bool $is_security_flaw_ignored)
encodePassword(string $raw, string $salt)
encode(string $raw, string $userSecret)
Generates a bcrypt encoded string.
Class for user password exception handling in ILIAS.
isPasswordTooLong(string $password)
Checks if the password is too long.
static getBytes($length)
Generate random bytes using OpenSSL or Mcrypt and mt_rand() as fallback.
setDataDirectory(string $data_directory)
__construct(Container $dic, ilPlugin $plugin)
isPasswordValid(string $encoded, string $raw, string $salt)