def main(): """Testing 'argon2-cffi' package""" parser = argparse.ArgumentParser() parser.add_argument( "-v", "--verbosity", dest="verbosity", action="count", default=0, help="set verbosity level", ) args = parser.parse_args() if args.verbosity == 1: logging.basicConfig(level=logging.INFO) elif args.verbosity > 1: logging.basicConfig(level=logging.DEBUG) else: logging.basicConfig(level=logging.ERROR) logging.debug(f"{argon2.__name__} {argon2.__version__}") ph = PasswordHasher() dummy_hash = ph.hash("s3kr3tp4ssw0rd") ph.verify(dummy_hash, "s3kr3tp4ssw0rd") ph.check_needs_rehash(dummy_hash)
def test_check_needs_rehash_no(self): """ Return False if the hash has the correct parameters. """ ph = PasswordHasher(1, 8, 1, 16, 16) assert not ph.check_needs_rehash(ph.hash("foo"))
def login(): """Retrieve a token""" data = request.get_json() user = User.query.filter_by(username=data["username"]).first() if not user: return jsonify({"message": "Invalid credentials"}), 401 try: hasher = PasswordHasher() hasher.verify(user.password, data["password"]) except (VerifyMismatchError, VerificationError, InvalidHash, AttributeError): # This must also return 401 or else the user may learn # private information return jsonify({"message": "Invalid credentials"}), 401 # Rehash password if the parameters of the PasswordHasher change. # https://argon2-cffi.readthedocs.io/en/stable/api.html if hasher.check_needs_rehash(user.password): user.password = hasher.hash(data["password"]) db.session.add(user) db.session.commit() access_token = create_access_token(identity=user.username, fresh=True) refresh_token = create_refresh_token(user.username) return (jsonify({ "access_token": access_token, "refresh_token": refresh_token }), 200)
def login(self, username, password): success = False msg = '' userID = 'N/A' key = 'N/A' self.cursor.execute( """select password, id from users where username = %s""", (username, )) if self.cursor.rowcount == 0: success = False msg += '\n\tUsername does not exists' elif self.cursor.rowcount != 1: success = False msg += '\n\tUnknown Error' else: row = self.cursor.fetchone() hashed = row[0] userID = row[1] verifier = PasswordHasher() try: verifier.verify(hashed, password) success = True except: success = False if success: msg += 'Authenticated' if verifier.check_needs_rehash(hashed): hashed = verifier.hash(password) count = self.cursor.execute( """update users set password = %s where username = %s""", (hashed, username)) if count != 1: msg += 'Failed to rehash password, contact support' logging.error('Failed to rehash password') self.db.rollback() else: self.db.commit() self.cursor.execute( """select activationKey from productKey where userID = %s""", (userID, )) row = self.cursor.fetchone() key = row[0] else: success = False msg += 'Incorrect Password' return success, msg, userID, key
def test_check_needs_rehash_yes(self): """ Return True if any of the parameters changes. """ ph = PasswordHasher(1, 8, 1, 16, 16) ph_old = PasswordHasher(1, 8, 1, 8, 8) assert ph.check_needs_rehash(ph_old.hash("foo"))
def login(): """Handle login requests On successfull login a session with a length of an hour should be generated. On failure an error should be thrown. """ hasher = PasswordHasher() payload = request.json username = payload['user'] password = payload['pass'] DBSessionMaker = sessionmaker(bind=engine) db_session = DBSessionMaker() try: user = db_session.query(Users).get(username) hasher.verify(user.password, password) # If the hash parameters are out of date or the user update the hash # One example of an out of date parameter is a insufficient time cost if hasher.check_needs_rehash(user.password): user.password = hasher.hash(password) # calculate an expiration time one our from now expire = datetime.utcnow() + timedelta(hours=1) session_id = uuid.uuid4().hex user_session = Sessions( # This must be a uuid4 or large cryptographically secure random number # A other formats of UUID can have several bytes perdicted presenting # a potential brute forcing risk # This implimentation choice assumes CPython is being used. id=session_id, username=username, session_expire=expire ) db_session.add(user_session) db_session.commit() auth_resp = Response(status=200) auth_resp.set_cookie('session', session_id) db_session.commit() return auth_resp # For security reasons only two responses may be provided # Thus errors do not need to be handled individually except Exception: db_session.rollback() # If we get to the end of this funtion something went wrong. # Thus make sure the user does not think they are logged in err_resp = Response(status=401) err_resp.delete_cookie('session') return err_resp
def compare_passwords(password, stored_password): """compares a plaintext password with the stored, encrypted password""" ph = PasswordHasher() try: ph.verify(stored_password, password) if ph.check_needs_rehash(stored_password): return (True, True) return (True, False) except VerifyMismatchError: return (False, False)
def test_type_is_configurable(self): """ Argon2id is default but can be changed. """ ph = PasswordHasher(time_cost=1, memory_cost=64) default_hash = ph.hash("foo") assert Type.ID is ph.type is ph._parameters.type assert Type.ID is extract_parameters(default_hash).type ph = PasswordHasher(time_cost=1, memory_cost=64, type=Type.I) assert Type.I is ph.type is ph._parameters.type assert Type.I is extract_parameters(ph.hash("foo")).type assert ph.check_needs_rehash(default_hash)
async def check_password(conn, name, password): """Check password and rotate cleartext password if needed""" from pakreq.utils import password_hash, password_verify users = await get_users(conn) status = False for user in users: if user['username'] == name or str(user['id']) == name: hash = user['password_hash'] if password_verify(user['id'], password, hash): status = True hasher = PasswordHasher() if hasher.check_needs_rehash(hash): hash = password_hash(user['id'], password) await update_user(conn, user['id'], password_hash=hash) return status
async def check_password(self, hasher: PasswordHasher, pwd: str, pepper: bytes) -> bool: """ compares a password to the password of the user :param hasher: PasswordHasher of MCWeb instance :param pwd: the entered password :param pepper: pepper of the MCWeb instance :return: whether the password is correct """ spp = self.salt + pwd.encode() + pepper try: if hasher.verify(self.password, spp): if hasher.check_needs_rehash(self.password): hsh = hasher.hash(spp) await self.db["user"].update_one({"_id": self.id}, {"$set": {"password": hsh}}) return True except VerifyMismatchError: return False
def verify_password(self): passwords = None write_back = False hasher = PasswordHasher() with open(os.path.join(DATA_DIR, PASSWORD_FILE), 'r', encoding='utf8', errors='ignore') as pw_file: try: passwords = json.load(pw_file) except json.decoder.JSONDecodeError: self.logger.error( f"Could not parse password file, make sure it exsits and is not corrupted" ) return False if self.user in passwords.keys(): try: hasher.verify(passwords[self.user], self.password) write_back = hasher.check_needs_rehash(passwords[self.user]) except VerifyMismatchError: self.logger.warning( f"Password verification failed for ID {self.user} from {self.client_ip} on port {self.client_port}" ) return False else: self.logger.warning( f"Password verification failed for ID {self.user} from {self.client_ip} on port {self.client_port}" ) return False if write_back: self.logger.info(f"Rehashing ID {self.user}'s password") passwords[self.user] = hasher.hash(self.password) with open(os.path.join(DATA_DIR, PASSWORD_FILE), 'w', encoding='utf8', errors='ignore') as pw_file: json.dump(passwords, pw_file, ensure_ascii=False, indent=4) self.logger.info( f"Password verification successful for ID {self.user} from {self.client_ip} on port {self.client_port}" ) return True
def verifyadmin(self, username, userpass): """ Verifies if the admin with the given username and password exists. Args: username (str): Username of admin to verify. userpass (str): Password of admin to verify. Retruns: True if the admin exists and valid password, otherwise false. Admin ID if the user exists and valid password, None otherwise. """ # Initialse session with self.sessionmanager() as session: # Initialise password hasher ph = PasswordHasher() # Query if admin exists admin = session.query(Admin).filter( Admin.username == username).first() # Check if query returns an admin if(admin is not None): # Verify whether the password is valid or not try: ph.verify(admin.passhash, userpass) except VerifyMismatchError: # Password does not match, return false return False, None # Check if password needs to be rehashed if(ph.check_needs_rehash(admin.passhash)): # Generate new hash rehash = ph.hash(userpass) # Update admin record to include new hash user.passhash = rehash # Since admin exists and password is valid, return true return True, admin.adminID else: # User doesn't exist, return false return False, None
def create_api_key(): """Creates a new API key and returns it to the user.""" # Create a new secret and hash it pass_hasher = PasswordHasher() secret = secrets.token_urlsafe(64) hash = pass_hasher.hash(secret) # verify the secret if not pass_hasher.verify(hash, secret): return False if pass_hasher.check_needs_rehash(hash): return False # insert the secret into the DB query = "INSERT INTO credential (password_hash) VALUES (%s) RETURNING credential_id" data = (hash, ) credential_id = insert(query, data, return_inserted_row_id=True) if not credential_id: return False return {'credential_id': credential_id, 'secret': secret}
def _verify_password(credentials: CredentialsDTO) -> None: user = repo.find_by_username(credentials.username) ph = PasswordHasher() ph.verify(user.password_hash, credentials.password) if ph.check_needs_rehash(user.password_hash): repo.update_pw_hash(user, ph.hash(credentials.password))
from argon2 import PasswordHasher ph = PasswordHasher() hash = ph.hash("s3kr3tp4ssw0rd") print(hash) # doctest: +SKIP #'$argon2id$v=19$m=102400,t=2,p=8$tSm+JOWigOgPZx/g44K5fQ$WDyus6py50bVFIPkjA28lQ' print(ph.verify(hash, "s3kr3tp4ssw0rd")) print(ph.check_needs_rehash(hash)) print(ph.verify(hash, "t0t411ywr0ng"))
def authenticate(): """ Authenticates the user with the supplied credentials, returning an authentication token for subsequent use with the api. --- parameters: - in: body name: credentials schema: type: object properties: email: type: string required: true password: type: string required: true responses: 200: schema: $ref: "#/definitions/Post" 401: description: Unauthorized """ body = request.json email = body.get("email") password = body.get("password") if email is None or password is None: return error(400, message="`email` and `password` fields" "are required.") user = User.query.filter(User.email == email).one_or_none() if user is None: return error(401, type="InvalidCredentials") hasher = PasswordHasher() try: hasher.verify(user.password_hash, password) except VerifyMismatchError: return error(401, type="InvalidCredentials") if hasher.check_needs_rehash(user.password_hash): hash = hasher.hash(password) user.password_hash = hash db.session.commit() if not user.confirmed: return error(401, type="Unconfirmed") now = time.time() expiration_time = now + 2 * 60 * 60 claims = { "iat": int(now), "exp": int(expiration_time), "iss": config.JWT_ISSUER, "aud": config.JWT_AUDIENCE, "sub": email, "rol": "admin" if user.role == UserRole.ADMIN else "normal", } token = encode(claims, config.JWT_SECRET_KEY, algorithm="HS256") return token_response(user.id, token.decode("utf-8"), user.role, int(expiration_time))
class AuthController: instance = None @staticmethod def get_instance(): if AuthController.instance is None: AuthController.instance = AuthController() return AuthController.instance def __init__(self): self.SECRET = "ABC123" self.ph = PasswordHasher() def authenticate(self, pw_hash: str, password: str) -> Dict: pw_verified: bool = self.ph.verify(pw_hash, password) new_pw_hash = None if pw_verified and self.ph.check_needs_rehash(pw_hash): new_pw_hash = self.ph.hash(password) if not pw_verified: raise Unauthorized("User or password is incorrect") return {'new_pw_hash': new_pw_hash, 'is_authenticated': True} def create_pw_hash(self, password: str) -> str: return self.ph.hash(password) def authorize(self, authorized_roles: List[str]): def decorated_function(function): @wraps(function) def wrapper(*args, **kwargs): self._has_authorized_roles(authorized_roles) return function(*args, **kwargs) return wrapper return decorated_function def _has_authorized_roles(self, authorized_roles: List[str]): # Check that auth cookie exist in request try: token = request.cookies.get("Authorization") except KeyError: raise Unauthorized("You need to be logged in") # Try to decode cookie to ensure user is authenticated user_data = self.decode_jwt_token(token) if (len(authorized_roles) == 0): return try: for role in authorized_roles: if role in user_data["roles"]: return raise Unauthorized("You are not authorized to perform this action") except KeyError: raise Unauthorized( "You don't have permission to perform this action") def create_jwt_token(self, user_id: int, username: str, roles: List = None) -> bytes: try: payload = { 'exp': datetime.utcnow() + timedelta(hours=3), 'iat': datetime.utcnow(), 'sub': dict(user_id=user_id, username=username, roles=roles), } return jwt.encode(payload, self.SECRET, algorithm='HS256') except Exception as e: print(e) def decode_jwt_token(self, jwt_token: bytes) -> Dict: if jwt_token is None: raise Unauthorized("You need to be signed in") try: payload = jwt.decode(jwt_token, self.SECRET, algorithms='HS256') return payload['sub'] except jwt.ExpiredSignatureError: raise Unauthorized("Token has expired, please sign in again") except jwt.InvalidTokenError: raise Unauthorized("Token is invalid, please sign in again")
class AuthAndToken: def __init__(self, sec_key, token_exp_in_minutes=60, time_cost=2, memory_cost=102400, parallelism=8, hash_len=16, salt_len=16): self.ph = PasswordHasher(time_cost, memory_cost, parallelism, hash_len, salt_len) self.secret_key = sec_key self.token_exp = token_exp_in_minutes def __verify_user(self, token): """ receives a jwt token and verifies it's parameters """ key = str(self.secret_key) try: jwt.decode(token, key, algorithms=['HS256']) except (jwt.ExpiredSignatureError, jwt.exceptions.DecodeError): return False return True def create_password(self, password): password = self.ph.hash(password) return password def verify_password(self, given_password, hash_password): """ Validates if a password is valid or not and checks if rehashing is required or not(if valid) :param: * username * supplied password :returns: * validation status (True/False) * updated password(if needed) else '' """ try: if self.ph.verify(hash_password.encode('utf-8'), given_password): if self.ph.check_needs_rehash(hash_password): # rehash password if needed hash_password = self.ph.hash(given_password) return True, hash_password return True, '' except (VerificationError, VerifyMismatchError): return False, '' def validate_request(self, required=None, token_name='token'): """ Decorator for validating Token and all required fields for a request :param: * required fields (if any): Dictionary of format: {<request_method> : [list of required fields]} => eg: {"POST": ["id", "password"]} * token name used in the header [default is 'token'] """ if required is None: required = {} def wrapper_layer2(func): @functools.wraps(func) @required_fields(required=required) def wrapper_layer1(*args, **kws): # checking JWT token token = request.headers.get(token_name) if not token: return { 'error': True, "message": "Token Required!", "token404": True }, 401 if not self.__verify_user(token): return { 'error': True, "message": "Invalid Token. Try logging in!", "unauth": True }, 403 return func(*args, **kws) return wrapper_layer1 return wrapper_layer2 def get_token(self, user): """ Generates a new JWT """ key = str(self.secret_key) claims = { 'user': user, 'time': str(datetime.datetime.utcnow()), 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=self.token_exp), } return jwt.encode(claims, key, algorithm='HS256') def get_user(self, token): claims = jwt.decode(token, verify=False) return str(claims['user'])
class Authenticator: """Class for registration and login of users.""" def __init__(self, mongo_db: Database): self._mongo_db = mongo_db self._pwd_hasher = PasswordHasher() def _is_email(self, login: str) -> bool: """Check if login is email or username. :param login: login from input. :type login: str :return: true if email, false if username. :rtype: bool """ return "@" in login def _rehash_password(self, user, password: str): """hash password again if Argon2 parameters change. :param user: User Document from mongo. :type user: Document :param password: Password to rehash. :type password: str """ try: new_hashed_pwd = self._pwd_hasher.hash(password) self._mongo_db.users.update_one( {"username": user.username}, {"$set": {"password": new_hashed_pwd}} ) except HashingError as err: logger.exception(err) def _create_token(self, size: int): return b64encode(urandom(size)).decode("utf-8") def register_user(self, username: str, password: str, email: str) -> dict: """Register a new user. :param username: Username of user. :type username: str :param password: Password of user. :type password: str :param email: Email of user. :type email: str :return: bool about failed/success login, user token and userID if success. :rtype: (bool, str) """ if not username: raise ValueError("cannot register user, didnt get username") if not password: raise ValueError("cannot register user, didnt get password") if not email: raise ValueError("cannot register user, didnt get email") exists = self._mongo_db.users.count_documents( {"$or": [{"username": username}, {"email": email}]}, limit=1 ) if exists > 0: return (False, "Username or email already registered.") try: token = self._create_token(128) _id = self._mongo_db.users.insert_one( { "username": username, "password": self._pwd_hasher.hash(password), "email": email, "created": datetime.now(), "last_login": datetime.now(), "tokens": [token], "collections": {}, } ).inserted_id return {"success": True, "token": token, "id": _id, "username": username} except HashingError as err: logger.exception(err) def login_user(self, login: str, password: str) -> dict: """Validate password and return dictionary according to result. :param login: Username or email of user. :type login: str :param password: Password of user. :type password: str :return: {success: bool, user: userID, error: str}. :rtype: dict """ user = ( self._mongo_db.users.find_one({"email": login}) if self._is_email(login) else self._mongo_db.users.find_one({"username": login}) ) if user is None: return {"success": False, "user": 0, "error": "Failed to verify user."} try: if self._pwd_hasher.verify(user["password"], password): # If user was verified, rehash password if it's needed. if self._pwd_hasher.check_needs_rehash(user["password"]): self._rehash_password(user, password) token = self._create_token(128) self._mongo_db.users.update_one( {"_id": ObjectId(user["_id"])}, {"$push": {"tokens": token}} ) return { "success": True, "id": str(user["_id"]), "token": token, "username": user["username"], } return {"success": False, "user": 0, "error": "Failed to verify user."} except (VerificationError, VerifyMismatchError) as err: logger.info(err) except InvalidHash as err: logger.exception(err) def logout_user(self, token: str, user_id: str) -> dict: """Logout user with deleting his login token from the database. :param token: Username or email of user. :type token: str :return: {success: bool, user: userID, error: str}. :rtype: dict """ user = self._mongo_db.users.update_one( {"_id": ObjectId(user_id)}, {"$pull": {"tokens": token}} ) if user.modified_count < 1: return {"success": False, "message": "User or token not found."} return {"success": True, "message": "token removed from database"}
debugmode = os.getenv("DEBUG") != None and os.getenv("DEBUG").lower() == "true" app = Flask(__name__) def firsttimepass(): password = input("What would you like to choose as a password? ") hash = ph.hash(password) with open("password.txt", "w") as f: f.write(hash) if os.path.isfile("password.txt"): with open("password.txt", "r") as f: hash = f.read() try: if ph.check_needs_rehash(hash): # invalid hash os.remove("password.txt") firsttimepass() except InvalidHash: os.remove("password.txt") firsttimepass() else: firsttimepass() def verify(hash, passw): try: ph.verify(hash, passw) except Exception as e: if isinstance(e,