ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
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 
41  private $data_directory = '';
42 
47  public function __construct(array $config = array())
48  {
49  if (!empty($config)) {
50  foreach ($config as $key => $value) {
51  switch (strtolower($key)) {
52  case 'ignore_security_flaw':
53  $this->setIsSecurityFlawIgnored($value);
54  break;
55 
56  case 'data_directory':
57  $this->setDataDirectory($value);
58  break;
59  }
60  }
61  }
62 
63  parent::__construct($config);
64  }
65 
69  protected function init()
70  {
71  $this->readClientSalt();
72  }
73 
77  protected function isBcryptSupported()
78  {
79  return PHP_VERSION_ID >= 50307;
80  }
81 
85  public function getDataDirectory()
86  {
87  return $this->data_directory;
88  }
89 
94  {
95  $this->data_directory = $data_directory;
96  }
97 
102  {
103  return (bool) $this->backward_compatibility;
104  }
105 
111  {
112  $this->backward_compatibility = (bool) $backward_compatibility;
113  }
114 
118  public function isSecurityFlawIgnored()
119  {
120  return (bool) $this->is_security_flaw_ignored;
121  }
122 
127  {
128  $this->is_security_flaw_ignored = (bool) $is_security_flaw_ignored;
129  }
130 
134  public function getClientSalt()
135  {
136  return $this->client_salt;
137  }
138 
142  public function setClientSalt($client_salt)
143  {
144  $this->client_salt = $client_salt;
145  }
146 
151  public function encodePassword($raw, $salt)
152  {
153  if (!$this->getClientSalt()) {
154  require_once 'Services/Password/exceptions/class.ilPasswordException.php';
155  throw new ilPasswordException('Missing client salt.');
156  }
157 
158  if ($this->isPasswordTooLong($raw)) {
159  require_once 'Services/Password/exceptions/class.ilPasswordException.php';
160  throw new ilPasswordException('Invalid password.');
161  }
162 
163  return $this->encode($raw, $salt);
164  }
165 
169  public function isPasswordValid($encoded, $raw, $salt)
170  {
171  if (!$this->getClientSalt()) {
172  require_once 'Services/Password/exceptions/class.ilPasswordException.php';
173  throw new ilPasswordException('Missing client salt.');
174  }
175 
176  return !$this->isPasswordTooLong($raw) && $this->check($encoded, $raw, $salt);
177  }
178 
182  public function getName()
183  {
184  return 'bcrypt';
185  }
186 
190  public function requiresSalt()
191  {
192  return true;
193  }
194 
195 
196 
200  public function requiresReencoding($encoded)
201  {
202  return false;
203  }
204 
212  protected function encode($raw, $user_secret)
213  {
214  $client_secret = $this->getClientSalt();
215  $hashed_password = hash_hmac('whirlpool', str_pad($raw, strlen($raw) * 4, sha1($user_secret), STR_PAD_BOTH), $client_secret, true);
216  $salt = substr(str_shuffle(str_repeat('./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 22)), 0, 22);
217 
222  if ($this->isBcryptSupported() && !$this->isBackwardCompatibilityEnabled()) {
223  $prefix = '$2y$';
224  } else {
225  $prefix = '$2a$';
226  // check if the password contains 8-bit character
227  if (!$this->isSecurityFlawIgnored() && preg_match('/[\x80-\xFF]/', $raw)) {
228  require_once 'Services/Password/exceptions/class.ilPasswordException.php';
229  throw new ilPasswordException(
230  'The bcrypt implementation used by PHP can contain a security flaw ' .
231  'using passwords with 8-bit characters. ' .
232  'We suggest to upgrade to PHP 5.3.7+ or use passwords with only 7-bit characters.'
233  );
234  }
235  }
236 
237  $salted_password = crypt($hashed_password, $prefix . $this->getCosts() . '$' . $salt);
238  if (strlen($salted_password) <= 13) {
239  require_once 'Services/Password/exceptions/class.ilPasswordException.php';
240  throw new ilPasswordException('Error during the bcrypt generation');
241  }
242 
243  return $salted_password;
244  }
245 
253  protected function check($encoded, $raw, $salt)
254  {
255  $hashed_password = hash_hmac('whirlpool', str_pad($raw, strlen($raw) * 4, sha1($salt), STR_PAD_BOTH), $this->getClientSalt(), true);
256  return crypt($hashed_password, substr($encoded, 0, 30)) == $encoded;
257  }
258 
262  public function getClientSaltLocation()
263  {
264  return $this->getDataDirectory() . '/' . self::SALT_STORAGE_FILENAME;
265  }
266 
270  private function readClientSalt()
271  {
272  if (is_file($this->getClientSaltLocation()) && is_readable($this->getClientSaltLocation())) {
273  $contents = file_get_contents($this->getClientSaltLocation());
274  if (strlen(trim($contents))) {
275  $this->setClientSalt($contents);
276  }
277  } else {
278  $this->generateClientSalt();
279  $this->storeClientSalt();
280  }
281  }
282 
286  private function generateClientSalt()
287  {
288  require_once 'Services/Password/classes/class.ilPasswordUtils.php';
289  $this->setClientSalt(
290  substr(str_replace('+', '.', base64_encode(ilPasswordUtils::getBytes(self::MIN_SALT_SIZE))), 0, 22)
291  );
292  }
293 
297  private function storeClientSalt()
298  {
299  $result = @file_put_contents($this->getClientSaltLocation(), $this->getClientSalt());
300  if (!$result) {
301  require_once 'Services/Password/exceptions/class.ilPasswordException.php';
302  throw new ilPasswordException(sprintf("Could not store the client salt in: %s. Please contact an administrator.", $this->getClientSaltLocation()));
303  }
304  }
305 }
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.
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...
$key
Definition: croninfo.php:18
check($encoded, $raw, $salt)
Verifies a bcrypt encoded string.
isPasswordTooLong($password)
Checks if the password is too long.