From 0d585e8a9930baa942ca58ac24bfad07e27e01a4 Mon Sep 17 00:00:00 2001 From: Jonny007-MKD Date: Sun, 8 Jan 2023 22:12:47 +0100 Subject: [PATCH] Encrypt passwords using bcrypt, keep sha256 for backwards compatibility The number of rounds can be specified by BCRYPT_ROUNDS environment variable. The default value is 14, as it takes 600 ms on my machine. As we currently only support bcrypt and plain sha256 hashes, we can distinguish them by the leading '$' of bcrypt hashes. Upgrading hashes on login is not yet implemented. --- app/__init__.py | 14 +++++++++----- app/glauth.py | 5 ++++- config.py | 9 ++++++++- requirements.txt | 1 + 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index b834aa7..fec1649 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -9,6 +9,7 @@ from flask_login import LoginManager from flask_mail import Mail from secrets import token_urlsafe +from passlib.hash import bcrypt import hashlib import os import click @@ -59,12 +60,14 @@ # Security - Generate GLAUTH compatible password hashs def generate_password_hash(password): - return hashlib.sha256(password.encode('utf-8')).hexdigest() + return bcrypt.using(rounds=app.config['BCRYPT_ROUNDS']).hash(password) def check_password_hash(hash, password): - if (hash != hashlib.sha256(password.encode('utf-8')).hexdigest()): - return False - return True + if not hash.startswith('$'): # plain SHA256 hashes were stored like this + return hash == hashlib.sha256(password.encode('utf-8')).hexdigest() + # TODO: Re-hash password with bcrypt and replace stored hash + + return bcrypt.verify(password, hash) from app import routes, models, glauth, adminview, errors @@ -76,4 +79,5 @@ def createdbdata(): click.echo('Creating Example DB') models.create_basic_db() else: - app.logger.info('Data in DB allready exists.') \ No newline at end of file + app.logger.info('Data in DB allready exists.') + diff --git a/app/glauth.py b/app/glauth.py index bcf2668..db3fdda 100644 --- a/app/glauth.py +++ b/app/glauth.py @@ -51,7 +51,10 @@ def create_glauth_config(): new_config += " mail = \"{}\"\n".format(user.mail) new_config += " uidnumber = {}\n".format(user.unixid) new_config += " primarygroup = {}\n".format(user.primarygroup) - new_config += " passsha256 = \"{}\"\n".format(user.password_hash) + if not user.password_hash.startswith('$'): # SHA256 hashes were stored like this + new_config += " passsha256 = \"{}\"\n".format(user.password_hash) + else: # bcrypt hashes have to be put into the config in hex representation + new_config += " passbcrypt = \"{}\"\n".format(user.password_hash.encode("utf-8").hex()) if len(user.othergroups) > 0: new_config += " otherGroups = [ {} ]\n".format(",".join(str(group.unixid) for group in user.othergroups)) if not user.is_active: diff --git a/config.py b/config.py index 1d1ebf4..4d8b695 100644 --- a/config.py +++ b/config.py @@ -1,10 +1,16 @@ import os basedir = os.path.abspath(os.path.dirname(__file__)) +def optional_str_to_int(s): + if s is None: return None + return int(s) + + class Config(object): APPNAME = os.environ.get('APPNAME') or 'Glauth UI' ORGANISATION = os.environ.get('ORGANISATION') or 'Glauth UI - Team' SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess' + BCRYPT_ROUNDS = optional_str_to_int(os.environ.get('BCRYPT_ROUNDS')) or 14 ADMIN_GROUP = os.environ.get('ADMIN_GROUP') or 'glauth_admin' # MAIL Config @@ -25,4 +31,5 @@ class Config(object): os.path.join(basedir, 'db', 'config.cfg') # FLASK ADMIN STUFF - FLASK_ADMIN_FLUID_LAYOUT = False \ No newline at end of file + FLASK_ADMIN_FLUID_LAYOUT = False + diff --git a/requirements.txt b/requirements.txt index 3925a5d..41d9625 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,3 +22,4 @@ python-dotenv==0.19.2 SQLAlchemy==1.4.29 Werkzeug==2.0.2 WTForms==3.0.1 +passlib==1.7.4