Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions install/config.php-template
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,20 @@ $gallery->setProfile(false);
*/
$gallery->setConfig('mode.maintenance', false);

/*
* Password hashing method. This defines the algorithm used for hashing new passwords.
* Valid options are:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(thanks for the documentation here!)

This is...a lot of options to offer, and I doubt most people (myself included!) are going to have a good idea of the tradeoffs between the choices. Obviously we need to keep "salted md5" for compatibility, but could we just pick one or two of the most secure other options?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we could always require users to only use what we allow them to use. but I do not know their systems, and what they need to use, why does gallery2 support phpass? why shouldn't bcrypt be supported, most people believe this is secure, why use or not use argon2*, it is believed to be more secure than bcrypt, but bcrypt and argon2 are not fips compliant. I have no idea what other software people would use with this, and what password hashing method they would use. I would not want to limit their options when it's trivial to implement.

* 'salted md5' (default, legacy Gallery 2)
* 'phpass'
* 'bcrypt'
* 'crypt-sha256'
* 'crypt-sha512'
* 'argon2i'
* 'argon2id'
*/
$gallery->setConfig('passwordHashMethod', 'salted md5');


/*
* Embedded mode. You can disable direct access to main.php (standalone G2)
* by setting this flag. Set value below to:
Expand Down
10 changes: 10 additions & 0 deletions modules/core/classes/Gallery.class
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,16 @@ class Gallery {
return $this->_config[$key];
}

/**
* Get if a key exists from the Gallery configuration settings
*
* @return boolean
*/
function isConfig($key) {
Comment thread
gregstoll marked this conversation as resolved.
assert(!empty($key));
return array_key_exists($key,$this->_config);
}

/**
* Initialize session.
*
Expand Down
30 changes: 29 additions & 1 deletion modules/core/classes/GalleryUser.class
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,34 @@ class GalleryUser extends GalleryEntity {
*/
function isCorrectPassword($password) {
$valid = $this->getHashedPassword();

/* Match crypt methods, which start with $x$ */
if (isset($valid[0]) && $valid[0] === '$') {
if (strlen($valid) == 34 && (strpos($valid, '$P$') === 0 || strpos($valid, '$H$') === 0)) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the PasswordHash only supports "$P$" prefixes - am I missing something?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, did not realised gallery2 had such an old version it didn't support the $H$ method

GalleryCoreApi::requireOnce('lib/phpass/PasswordHash.inc');
$hashGenerator = new PasswordHash(10, true);
return $hashGenerator->CheckPassword($password, $valid);
}

if (function_exists('password_verify')) {
if (preg_match('/^\$(2[axy]|argon2i|argon2id)\$/', $valid)) {
return password_verify($password, $valid);
}
}

$hash = crypt($password, $valid);
if (function_exists('hash_equals')) {
if (hash_equals($valid, $hash)) {
return true;
}
} else {
if ($hash === $valid) {
return true;
}
}
return false;
}

$salt = substr($valid, 0, 4);
/* Support both old (G1 thru 1.4.0; G2 thru alpha-4) and new password schemes: */
$guess = (strlen($valid) == 32) ? md5($password) : ($salt . md5($salt . $password));
Expand Down Expand Up @@ -203,7 +231,7 @@ class GalleryUser extends GalleryEntity {
* @param string $newPassword a plaintext password
*/
function changePassword($newPassword) {
$this->setHashedPassword(GalleryUtilities::md5Salt($newPassword));
$this->setHashedPassword(GalleryUtilities::hashPassword($newPassword));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a handful of uses of md5Salt() in modules/core/test/phpunit - do those need to be updated too?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, must of missed these.

}

/**
Expand Down
71 changes: 71 additions & 0 deletions modules/core/classes/GalleryUtilities.class
Original file line number Diff line number Diff line change
Expand Up @@ -1290,9 +1290,80 @@ class GalleryUtilities {
* @return boolean true if correct
*/
static function isCorrectPassword($guess, $hashedPassword) {
/* Match crypt methods, which start with $x$ */
if (isset($hashedPassword[0]) && $hashedPassword[0] === '$') {
if (strlen($hashedPassword) == 34 && (strpos($hashedPassword, '$P$') === 0 || strpos($hashedPassword, '$H$') === 0)) {
GalleryCoreApi::requireOnce('lib/phpass/PasswordHash.inc');
$hashGenerator = new PasswordHash(10, true);
return $hashGenerator->CheckPassword($guess, $hashedPassword);
}

if (function_exists('password_verify')) {
if (preg_match('/^\$(2[axy]|argon2i|argon2id)\$/', $hashedPassword)) {
return password_verify($guess, $hashedPassword);
}
}

$hash = crypt($guess, $hashedPassword);
if (function_exists('hash_equals')) {
if (hash_equals($hashedPassword, $hash)) {
return true;
}
} else {
if ($hash === $hashedPassword) {
return true;
}
}
return false;
}

return (GalleryUtilities::md5Salt($guess, $hashedPassword) === $hashedPassword);
}

/**
* Create a hashed password using the configured hashing algorithm.
* @param string $password plaintext password
* @return string hashed password
*/
static function hashPassword($password) {
global $gallery;
if ($gallery->isConfig('passwordHashMethod')) {
Comment thread
gregstoll marked this conversation as resolved.
$method = $gallery->getConfig('passwordHashMethod');
}
if (empty($method)) {
$method = 'salted md5';
}

if ($method == 'phpass') {
GalleryCoreApi::requireOnce('lib/phpass/PasswordHash.inc');
$hashGenerator = new PasswordHash(10, true);
return $hashGenerator->HashPassword($password);
} else if ($method == 'bcrypt') {
if (function_exists('password_hash')) {
return password_hash($password, PASSWORD_BCRYPT);
} else {
$chars = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$salt = '$2y$10$';
for ($i = 0; $i < 22; $i++) {
$salt .= $chars[mt_rand(0, 63)];
Comment thread
gregstoll marked this conversation as resolved.
}
return crypt($password, $salt);
}
} else if ($method == 'crypt-sha256') {
$salt = '$5$rounds=5000$' . substr(md5(mt_rand()), 0, 16) . '$';
return crypt($password, $salt);
} else if ($method == 'crypt-sha512') {
$salt = '$6$rounds=5000$' . substr(md5(mt_rand()), 0, 16) . '$';
return crypt($password, $salt);
} else if ($method == 'argon2i' && defined('PASSWORD_ARGON2I')) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user specifies "argon2i" but PASSWORD_ARGON2I isn't defined, this has us fall back to an insecure option. Can we return an error somehow instead? (same for "argon2id" below)

return password_hash($password, PASSWORD_ARGON2I);
} else if ($method == 'argon2id' && defined('PASSWORD_ARGON2ID')) {
return password_hash($password, PASSWORD_ARGON2ID);
}

return GalleryUtilities::md5Salt($password);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, would be nice to check that $method is actually "salted md5" here and the user didn't just make a typo.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would make more sense to add this somewhere where the config file is loaded and validated then. it can check for a typo and error, and validate if argon2 is selected that they do exist. Otherwise the admin would never know about it until a password was changed.

}

/**
* Verify that the API provided is compatible with the API that we require.
*
Expand Down
2 changes: 1 addition & 1 deletion modules/password/classes/PasswordHelper.class
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class PasswordHelper extends PasswordInterface_1_0 {

/* Save hashed password */
$ret = GalleryCoreApi::setPluginParameter('module', 'password',
'password', GalleryUtilities::md5Salt($password), $item->getId());
'password', GalleryUtilities::hashPassword($password), $item->getId());
if ($ret) {
return $ret;
}
Expand Down
2 changes: 1 addition & 1 deletion modules/register/classes/GalleryPendingUser.class
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class GalleryPendingUser extends GalleryUser {
* @param string $newPassword a plaintext password
*/
function changePassword($newPassword) {
$this->setHashedPassword(GalleryUtilities::md5Salt($newPassword));
$this->setHashedPassword(GalleryUtilities::hashPassword($newPassword));
}

/**
Expand Down