ILIAS  release_8 Revision v8.24
class.ilBcryptPasswordEncoder.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
28{
30 private const MIN_SALT_SIZE = 16;
31
33 public const SALT_STORAGE_FILENAME = 'pwsalt.txt';
34
35 private ?string $client_salt = null;
36 private bool $is_security_flaw_ignored = false;
37 private bool $backward_compatibility = false;
38 private string $data_directory = '';
39
44 public function __construct(array $config = [])
45 {
46 foreach ($config as $key => $value) {
47 $key = strtolower($key);
48 if ($key === 'ignore_security_flaw') {
49 $this->setIsSecurityFlawIgnored($value);
50 } elseif ($key === 'data_directory') {
51 $this->setDataDirectory($value);
52 }
53 }
54
56 }
57
58 protected function init(): void
59 {
60 $this->readClientSalt();
61 }
62
63 protected function isBcryptSupported(): bool
64 {
65 return PHP_VERSION_ID >= 50307;
66 }
67
68 public function getDataDirectory(): string
69 {
71 }
72
73 public function setDataDirectory(string $data_directory): void
74 {
75 $this->data_directory = $data_directory;
76 }
77
78 public function isBackwardCompatibilityEnabled(): bool
79 {
81 }
82
87 {
88 $this->backward_compatibility = $backward_compatibility;
89 }
90
91 public function isSecurityFlawIgnored(): bool
92 {
94 }
95
97 {
98 $this->is_security_flaw_ignored = $is_security_flaw_ignored;
99 }
100
101 public function getClientSalt(): ?string
102 {
103 return $this->client_salt;
104 }
105
106 public function setClientSalt(?string $client_salt): void
107 {
108 $this->client_salt = $client_salt;
109 }
110
111 public function encodePassword(string $raw, string $salt): string
112 {
113 if (!$this->getClientSalt()) {
114 throw new ilPasswordException('Missing client salt.');
115 }
116
117 if ($this->isPasswordTooLong($raw)) {
118 throw new ilPasswordException('Invalid password.');
119 }
120
121 return $this->encode($raw, $salt);
122 }
123
124 public function isPasswordValid(string $encoded, string $raw, string $salt): bool
125 {
126 if (!$this->getClientSalt()) {
127 throw new ilPasswordException('Missing client salt.');
128 }
129
130 return !$this->isPasswordTooLong($raw) && $this->check($encoded, $raw, $salt);
131 }
132
133 public function getName(): string
134 {
135 return 'bcrypt';
136 }
137
138 public function requiresSalt(): bool
139 {
140 return true;
141 }
142
143 public function requiresReencoding(string $encoded): bool
144 {
145 return false;
146 }
147
148 protected function encode(string $raw, string $userSecret): string
149 {
150 $clientSecret = $this->getClientSalt();
151 $hashedPassword = hash_hmac(
152 'whirlpool',
153 str_pad($raw, strlen($raw) * 4, sha1($userSecret), STR_PAD_BOTH),
154 $clientSecret,
155 true
156 );
157 $salt = substr(
158 str_shuffle(str_repeat('./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 22)),
159 0,
160 22
161 );
162
167 if ($this->isBcryptSupported() && !$this->isBackwardCompatibilityEnabled()) {
168 $prefix = '$2y$';
169 } else {
170 $prefix = '$2a$';
171 // check if the password contains 8-bit character
172 if (!$this->isSecurityFlawIgnored() && preg_match('#[\x80-\xFF]#', $raw)) {
173 throw new ilPasswordException(
174 'The bcrypt implementation used by PHP can contain a security flaw ' .
175 'using passwords with 8-bit characters. ' .
176 'We suggest to upgrade to PHP 5.3.7+ or use passwords with only 7-bit characters.'
177 );
178 }
179 }
180
181 $saltedPassword = crypt($hashedPassword, $prefix . $this->getCosts() . '$' . $salt);
182 if (strlen($saltedPassword) <= 13) {
183 throw new ilPasswordException('Error during the bcrypt generation');
184 }
185
186 return $saltedPassword;
187 }
188
189 protected function check(string $encoded, string $raw, string $salt): bool
190 {
191 $hashedPassword = hash_hmac(
192 'whirlpool',
193 str_pad($raw, strlen($raw) * 4, sha1($salt), STR_PAD_BOTH),
194 $this->getClientSalt(),
195 true
196 );
197
198 return $this->comparePasswords($encoded, crypt($hashedPassword, substr($encoded, 0, 30)));
199 }
200
201 public function getClientSaltLocation(): string
202 {
203 return $this->getDataDirectory() . '/' . self::SALT_STORAGE_FILENAME;
204 }
205
206 private function readClientSalt(): void
207 {
208 if (is_file($this->getClientSaltLocation()) && is_readable($this->getClientSaltLocation())) {
209 $contents = file_get_contents($this->getClientSaltLocation());
210 if ($contents !== false && trim($contents) !== '') {
211 $this->setClientSalt($contents);
212 }
213 } else {
214 $this->generateClientSalt();
215 $this->storeClientSalt();
216 }
217 }
218
219 private function generateClientSalt(): void
220 {
221 $this->setClientSalt(
222 substr(str_replace('+', '.', base64_encode(ilPasswordUtils::getBytes(self::MIN_SALT_SIZE))), 0, 22)
223 );
224 }
225
226 private function storeClientSalt(): void
227 {
229
230 set_error_handler(static function (int $severity, string $message, string $file, int $line): void {
231 throw new ErrorException($message, $severity, $severity, $file, $line);
232 });
233
234 try {
235 $result = file_put_contents($location, $this->getClientSalt());
236 if (!$result) {
237 throw new ilPasswordException(sprintf(
238 'Could not store the client salt in: %s. Please contact an administrator.',
240 ));
241 }
242 } catch (Exception $e) {
243 throw new ilPasswordException(sprintf(
244 'Could not store the client salt in: %s. Please contact an administrator.',
246 ));
247 } finally {
248 restore_error_handler();
249 }
250 }
251}
$location
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: buildRTE.php:22
comparePasswords(string $knownString, string $userString)
Compares two passwords.
encodePassword(string $raw, string $salt)
Encodes the raw password.
getName()
Returns a unique name/id of the concrete password encoder.
setBackwardCompatibility(bool $backward_compatibility)
Set the backward compatibility $2a$ instead of $2y$ for PHP 5.3.7+.
encode(string $raw, string $userSecret)
requiresSalt()
Returns whether the encoder requires a salt.
isPasswordValid(string $encoded, string $raw, string $salt)
Checks a raw password against an encoded password.
setDataDirectory(string $data_directory)
requiresReencoding(string $encoded)
Returns whether the encoded password needs to be re-encoded.
check(string $encoded, string $raw, string $salt)
setIsSecurityFlawIgnored(bool $is_security_flaw_ignored)
Class for user password exception handling in ILIAS.
static getBytes(int $length)
Generate random bytes using OpenSSL or Mcrypt and mt_rand() as fallback.
if(!array_key_exists('PATH_INFO', $_SERVER)) $config
Definition: metadata.php:85
__construct(Container $dic, ilPlugin $plugin)
@inheritDoc
string $key
Consumer key/client ID value.
Definition: System.php:193
$message
Definition: xapiexit.php:32