diff --git a/app/core/security.py b/app/core/security.py index e521129cc..9d82283a1 100644 --- a/app/core/security.py +++ b/app/core/security.py @@ -1,9 +1,9 @@ import secrets from datetime import datetime, timedelta +import bcrypt from fastapi.security import OAuth2AuthorizationCodeBearer from jose import jwt -from passlib.context import CryptContext from sqlalchemy.ext.asyncio import AsyncSession from app.core.config import Settings @@ -11,12 +11,10 @@ from app.models import models_core from app.schemas import schemas_auth -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto", bcrypt__rounds=13) """ -In order to salt and hash password, we use a *passlib* [CryptContext](https://passlib.readthedocs.io/en/stable/narr/quickstart.html) object. +In order to salt and hash password, we the bcrypt hashing function (see https://en.wikipedia.org/wiki/Bcrypt). -We use "bcrypt" to hash password, a different hash will be added automatically for each password. See [Auth0 Understanding bcrypt](https://auth0.com/blog/hashing-in-action-understanding-bcrypt/) for information about bcrypt. -deprecated="auto" may be used to do password hash migration, see [Passlib hash migration](https://passlib.readthedocs.io/en/stable/narr/context-tutorial.html#deprecation-hash-migration). +A different salt will be added automatically for each password. See [Auth0 Understanding bcrypt](https://auth0.com/blog/hashing-in-action-understanding-bcrypt/) for information about bcrypt. It is important to use enough rounds while accounting for the hash computation time. Default is 12. 13 allows for a 0.5 seconds computing delay. """ @@ -36,31 +34,38 @@ """ -def get_password_hash(password: str) -> str: +def generate_token(nbytes=32) -> str: """ - Return a salted hash computed from password. The function use a bcrypt based *passlib* CryptContext. - Both the salt and the algorithm identifier are included in the hash. + Generate a `nbytes` bytes cryptographically strong random urlsafe token using the *secrets* library. + + By default, a 32 bytes token is generated. """ - return pwd_context.hash(password) + # We use https://docs.python.org/3/library/secrets.html#secrets.token_urlsafe to generate the activation secret token + return secrets.token_urlsafe(nbytes) -def verify_password(plain_password: str, hashed_password: str | None) -> bool: +def get_password_hash(password: str) -> str: """ - Compare `plain_password` against its salted hash representation `hashed_password`. The function use a bcrypt based *passlib* CryptContext. - - Pass hashed_password=None to simulate the delay a real verification would have taken. This is useful to limit timing attacks + Return a salted hash computed from password. + Both the salt and the algorithm identifier are included in the hash. """ - return pwd_context.verify(plain_password, hashed_password) + hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt(rounds=13)) + return hashed.decode("utf-8") -def generate_token(nbytes=32) -> str: +def verify_password(plain_password: str, hashed_password: str | None) -> bool: """ - Generate a `nbytes` bytes cryptographically strong random urlsafe token using the *secrets* library. + Compare `plain_password` against its salted hash representation `hashed_password`. - By default, a 32 bytes token is generated. + We genrerate a fake_hash for the case where hashed_password=None (ie the email isn't valid) to simulate the delay a real verification would have taken. + This is useful to limit timing attacks that could be used to guess valid emails. """ - # We use https://docs.python.org/3/library/secrets.html#secrets.token_urlsafe to generate the activation secret token - return secrets.token_urlsafe(nbytes) + fake_hash = bcrypt.hashpw(generate_token(12).encode("utf-8"), bcrypt.gensalt(13)) + if hashed_password is None: + return bcrypt.checkpw(plain_password.encode("utf-8"), fake_hash) + return bcrypt.checkpw( + plain_password.encode("utf-8"), hashed_password.encode("utf-8") + ) async def authenticate_user( diff --git a/requirements.txt b/requirements.txt index e8c2be2b1..0da1c3f3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ alembic==1.11.3 # database migrations fastapi==0.99.0 Jinja2==3.1.2 # template engine for html files -passlib[bcrypt]==1.7.4 # password hashing using bcrypt +bcrypt==4.0.1 # password hashing python-dotenv==1.0.0 # load environment variables from .env file python-jose[cryptography]==3.3.0 # generate and verify the JWT tokens python-multipart==0.0.6 # a form data parser, as oauth flow requires form-data parameters