Skip to content

Password Management

Yannick Warnier edited this page Mar 23, 2020 · 6 revisions

Note: This page is useful for all Chamilo 1.10.* and 1.11.* versions. Other versions might be affected by some slight differences. Older versions might not include the bcrypt option and actually be much easier to understand.

Every once in a while, we have to deal with passwords encryption and, because the password generation mechanism depends on deeply nested Symfony security code, we lose quite some time trying to remember how that stuff works.

For example, you might need to write a script that will generate new temporary passwords for your users, or you might want to check if passwords cannot be guessed too easily, for example comparing them to the string "12345678".

In order to do that, you need to understand how passwords work and how you can reproduce the hashing algorithm.

Password generation

Just so you know, the final processing of a password is done, in Chamilo, through the MessageDigestPasswordEncoder::encodePassword() method, found in vendor/symfony/security/Core/Encoder/MessageDigestPasswordEncoder.php (just as expected, right?).

So it's not part of the Git code of Chamilo, and will only exist if you downloaded one of the stable packages, or executed the dependency manager "composer".

But if we know that, we don't need to retrace everything. All we need to know is that this method will depend on 3 "parameters" set in the constructor of the class:

  • the algorithm
  • whether to encode as base64
  • the number of iterations (because most encryption methods today decide on a number of times the password will be hashed, instead of just hashing it once)

This can be traced back to several points in the Chamilo code, like a call to UserManager::isPasswordValid() in main/auth/profile.php.

isPasswordValid() (and other related methods) will first get the encryption algorithm from app/config/configuration.php (setting 'password_encryption'), then send the raw password and the salt (a random string assigned to each user account and found in the database table "user") to src/Chamilo/UserBundle/Security/Encoder.php.

This same file uses (and this is an important point) the following classes:

use Symfony\Component\Security\Core\Encoder\BCryptPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
use Symfony\Component\Security\Core\Encoder\PlaintextPasswordEncoder;

So depending on the hash algorithm you are using, one of these will be used. In our case, because bcrypt is the default, we will go with this one.

In this same Encoder.php file again, we find (in the constructor) that the "cost" for bcrypt (or the number of iterations, as it is called in other places) is set to 4 (hardcoded).

We also learn in BCryptPasswordEncoder::encodePassword() that the real hashing method, in the end, is:

return password_hash($raw, PASSWORD_BCRYPT, $options);

Where $options is, basically, the cost of "4" and the salt (combined in an array), and that "PASSWORD_BCRYPT" is a constant for the PHP function "password_hash()".

We have no further to go: if we want to generate a new password from a raw (clear) password contained in $raw and a salt string (obtained from the user table), we just need to do this:

$options['salt'] = $salt;
$options['cost'] = 4;
$password = password_hash($raw, PASSWORD_BCRYPT, $options);

Password comparison

If you want to compare a freshly generated string to the hashed password, you cannot just use a PHP string comparison. You will need to call the password_verify() function of PHP, which is nicely encapsulated (in the case of bcrypt) in a call like:

UserManager::isPasswordValid($hashedPassword, $raw, null);

This will return true if the raw password matches the hashed password, and false otherwise.

Default password check

During the default login process in Chamilo, the password is checked in main/inc/local.inc.php.

So a good solution if you were locked out of your portal and had access to modify local.inc.php, you could simply replace calls to isPasswordValid() by "true" (that would enable anyone with a valid username to login with any password, though). This is why it is important to avoid insecure access (like FTP) to your server files.

Clone this wiki locally