From bf9fdf6e7ed4caac8a8bde062e4663d81302f6cd Mon Sep 17 00:00:00 2001 From: Patrick Domack Date: Sun, 3 May 2026 00:19:35 -0400 Subject: [PATCH] Adds support for something better than md5 hashes. --- install/config.php-template | 14 ++++ modules/core/classes/Gallery.class | 10 +++ modules/core/classes/GalleryUser.class | 30 +++++++- modules/core/classes/GalleryUtilities.class | 71 +++++++++++++++++++ modules/password/classes/PasswordHelper.class | 2 +- .../register/classes/GalleryPendingUser.class | 2 +- 6 files changed, 126 insertions(+), 3 deletions(-) diff --git a/install/config.php-template b/install/config.php-template index e63ab972..fb769073 100644 --- a/install/config.php-template +++ b/install/config.php-template @@ -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: + * '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: diff --git a/modules/core/classes/Gallery.class b/modules/core/classes/Gallery.class index 8839274c..1fd51d85 100644 --- a/modules/core/classes/Gallery.class +++ b/modules/core/classes/Gallery.class @@ -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) { + assert(!empty($key)); + return array_key_exists($key,$this->_config); + } + /** * Initialize session. * diff --git a/modules/core/classes/GalleryUser.class b/modules/core/classes/GalleryUser.class index e51e1ed0..92493fad 100644 --- a/modules/core/classes/GalleryUser.class +++ b/modules/core/classes/GalleryUser.class @@ -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)) { + 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)); @@ -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)); } /** diff --git a/modules/core/classes/GalleryUtilities.class b/modules/core/classes/GalleryUtilities.class index 00bfc720..dae58eb8 100644 --- a/modules/core/classes/GalleryUtilities.class +++ b/modules/core/classes/GalleryUtilities.class @@ -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')) { + $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)]; + } + 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')) { + return password_hash($password, PASSWORD_ARGON2I); + } else if ($method == 'argon2id' && defined('PASSWORD_ARGON2ID')) { + return password_hash($password, PASSWORD_ARGON2ID); + } + + return GalleryUtilities::md5Salt($password); + } + /** * Verify that the API provided is compatible with the API that we require. * diff --git a/modules/password/classes/PasswordHelper.class b/modules/password/classes/PasswordHelper.class index 3c66775a..aa8f4907 100644 --- a/modules/password/classes/PasswordHelper.class +++ b/modules/password/classes/PasswordHelper.class @@ -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; } diff --git a/modules/register/classes/GalleryPendingUser.class b/modules/register/classes/GalleryPendingUser.class index 7e6c9001..e7e337cb 100644 --- a/modules/register/classes/GalleryPendingUser.class +++ b/modules/register/classes/GalleryPendingUser.class @@ -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)); } /**