Skip to content

Password Management

Yannick Warnier edited this page Feb 14, 2023 · 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.

Minimum password requirements

The following settings require the app/config/profile.conf.php to include define('CHECK_PASS_EASY_TO_FIND', true);.

Characters

A $_configuration['password_requirements'] parameter in app/config/configuration.php allows you to define minimum requirements like number of lowercase, uppercase, numeric and special characters as well as a minimum length.

Password change requirements

$_configuration['password_requirements']['force_different_password'] allows you to make sure users set a different password than the previous one when requested to change password.

Individual passwords

To ensure each user has an individual password, let the system generate passwords. It will automatically generate a random string that matches the password_requirements.

Lockout for failed login attempts

The $_configuration['login_max_attempt_before_blocking_account'] setting in app/config/configuration.php allows you to set a maximum number of login attempts, but this requires the track_e_login_attempt table to be created and a change in entities to be made. Check the configuration.php file for details.

Once the user is locked out, only an admin can reactivate their account.

Change password on first login

You can require users to change their password during their first login by enabling the $_configuration['force_renew_password_at_first_login'] setting in app/config/configuration.php and adding the checkbox extra field ask_new_password.

Password max lifetime

Not implemented yet.

Preventing well-known passwords

Not implemented yet.

Multi-Factor Authentication

Not implemented yet.

Clone this wiki locally