Use of a broken or weak cryptographic hashing algorithm (SHA256) on password storage #2175

This commit is contained in:
nicolargo 2023-01-30 09:29:32 +01:00
commit 6876490d24
5 changed files with 42 additions and 30 deletions

View File

@ -75,7 +75,7 @@ class GlancesClientBrowser(object):
# Try with the preconfigure password (only if status is PROTECTED)
clear_password = self.password.get_password(server['name'])
if clear_password is not None:
server['password'] = self.password.sha256_hash(clear_password)
server['password'] = self.password.get_hash(clear_password)
return 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port'])
else:
return 'http://{}:{}'.format(server['ip'], server['port'])
@ -151,7 +151,7 @@ class GlancesClientBrowser(object):
)
# Store the password for the selected server
if clear_password is not None:
self.set_in_selected('password', self.password.sha256_hash(clear_password))
self.set_in_selected('password', self.password.get_hash(clear_password))
# Display the Glance client on the selected server
logger.info("Connect Glances client to the {} server".format(server['key']))

View File

@ -68,6 +68,10 @@ if PY3:
return s.decode()
return s.encode('ascii', 'ignore').decode()
def to_hex(s):
"""Convert the bytes string to a hex string"""
return s.hex()
def listitems(d):
return list(d.items())
@ -166,6 +170,10 @@ else:
return s
return unicodedata.normalize('NFKD', s).encode('ascii', 'ignore')
def to_hex(s):
"""Convert the string to a hex string in Python 2"""
return s.encode('hex')
def listitems(d):
return d.items()

View File

@ -127,8 +127,10 @@ class GlancesBottle(object):
if username == self.args.username:
from glances.password import GlancesPassword
pwd = GlancesPassword(username=username, config=self.config)
return pwd.check_password(self.args.password, pwd.sha256_hash(password))
pwd = GlancesPassword(username=username,
config=self.config)
return pwd.check_password(self.args.password,
pwd.get_hash(password))
else:
return False

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@ import sys
import uuid
from io import open
from glances.compat import b, input
from glances.compat import b, input, to_hex
from glances.config import user_config_dir
from glances.globals import safe_makedirs
from glances.logger import logger
@ -36,25 +36,26 @@ class GlancesPassword(object):
def local_password_path(self):
"""Return the local password path.
Related toissue: Password files in same configuration dir in effect #2143
Related to issue: Password files in same configuration dir in effect #2143
"""
if self.config is None:
return user_config_dir()
else:
return self.config.get_value('passwords', 'local_password_path', default=user_config_dir())
def sha256_hash(self, plain_password):
"""Return the SHA-256 of the given password."""
return hashlib.sha256(b(plain_password)).hexdigest()
def get_hash(self, salt, plain_password):
"""Return the hashed password, salt + SHA-256."""
return hashlib.sha256(salt.encode() + plain_password.encode()).hexdigest()
def get_hash(self, plain_password, salt=''):
"""Return the hashed password, salt + pbkdf2_hmac."""
return to_hex(hashlib.pbkdf2_hmac('sha256',
plain_password.encode(),
salt.encode(),
100000,
dklen=128))
def hash_password(self, plain_password):
"""Hash password with a salt based on UUID (universally unique identifier)."""
salt = uuid.uuid4().hex
encrypted_password = self.get_hash(salt, plain_password)
encrypted_password = self.get_hash(plain_password,
salt=salt)
return salt + '$' + encrypted_password
def check_password(self, hashed_password, plain_password):
@ -63,7 +64,8 @@ class GlancesPassword(object):
Return the comparison with the encrypted_password.
"""
salt, encrypted_password = hashed_password.split('$')
re_encrypted_password = self.get_hash(salt, plain_password)
re_encrypted_password = self.get_hash(plain_password,
salt = salt)
return encrypted_password == re_encrypted_password
def get_password(self, description='', confirm=False, clear=False):
@ -72,11 +74,11 @@ class GlancesPassword(object):
For Glances server, get the password (confirm=True, clear=False):
1) from the password file (if it exists)
2) from the CLI
Optionally: save the password to a file (hashed with salt + SHA-256)
Optionally: save the password to a file (hashed with salt + SHA-pbkdf2_hmac)
For Glances client, get the password (confirm=False, clear=True):
1) from the CLI
2) the password is hashed with SHA-256 (only SHA string transit
2) the password is hashed with SHA-pbkdf2_hmac (only SHA string transit
through the network)
"""
if os.path.exists(self.password_file) and not clear:
@ -84,21 +86,21 @@ class GlancesPassword(object):
logger.info("Read password from file {}".format(self.password_file))
password = self.load_password()
else:
# password_sha256 is the plain SHA-256 password
# password_hashed is the salt + SHA-256 password
password_sha256 = self.sha256_hash(getpass.getpass(description))
password_hashed = self.hash_password(password_sha256)
# password_hash is the plain SHA-pbkdf2_hmac password
# password_hashed is the salt + SHA-pbkdf2_hmac password
password_hash = self.get_hash(getpass.getpass(description))
password_hashed = self.hash_password(password_hash)
if confirm:
# password_confirm is the clear password (only used to compare)
password_confirm = self.sha256_hash(getpass.getpass('Password (confirm): '))
password_confirm = self.get_hash(getpass.getpass('Password (confirm): '))
if not self.check_password(password_hashed, password_confirm):
logger.critical("Sorry, passwords do not match. Exit.")
sys.exit(1)
# Return the plain SHA-256 or the salted password
# Return the plain SHA-pbkdf2_hmac or the salted password
if clear:
password = password_sha256
password = password_hash
else:
password = password_hashed