diff --git a/apikey/auth.py b/apikey/auth.py index 3d45a4eb..00212c1b 100644 --- a/apikey/auth.py +++ b/apikey/auth.py @@ -4,14 +4,12 @@ from ninja.security import APIKeyHeader from apikey.hashers import get_hasher from apikey.models import ApiClient, ApiKey -_UUID_LENGTH = 36 - class ApiKeyAuth(APIKeyHeader): param_name = "X-APIKey" def authenticate(self, request: HttpRequest, key: str | None) -> ApiClient | None: - if not key or len(key) != _UUID_LENGTH: + if not key or len(key) != ApiKey.KEY_LENGTH: return None hasher = get_hasher() hashed_key = hasher.encode(key) diff --git a/apikey/hashers.py b/apikey/hashers.py index 3a623177..95c16673 100644 --- a/apikey/hashers.py +++ b/apikey/hashers.py @@ -1,12 +1,12 @@ import functools import hashlib -import uuid +import secrets from django.contrib.auth.hashers import BasePasswordHasher from django.utils.crypto import constant_time_compare -class Sha256ApiKeyHasher(BasePasswordHasher): +class Sha512ApiKeyHasher(BasePasswordHasher): """ An API key hasher using the sha256 algorithm. @@ -15,14 +15,14 @@ class Sha256ApiKeyHasher(BasePasswordHasher): high entropy, randomly generated API keys. """ - algorithm = "sha256" + algorithm = "sha512" def salt(self) -> str: # No need for a salt on a high entropy key. return "" def encode(self, password: str, salt: str = "") -> str: - hashed = hashlib.sha256(password.encode()).hexdigest() + hashed = hashlib.sha512(password.encode()).hexdigest() return f"{self.algorithm}$${hashed}" def verify(self, password: str, encoded: str) -> bool: @@ -32,11 +32,12 @@ class Sha256ApiKeyHasher(BasePasswordHasher): @functools.cache def get_hasher(): - return Sha256ApiKeyHasher() + return Sha512ApiKeyHasher() def generate_key() -> tuple[str, str]: """Generate a [key, hash] couple.""" - key = str(uuid.uuid4()) + # this will result in key with a length of 72 + key = str(secrets.token_urlsafe(54)) hasher = get_hasher() return key, hasher.encode(key) diff --git a/apikey/migrations/0001_initial.py b/apikey/migrations/0001_initial.py index cda9e9f6..b416b749 100644 --- a/apikey/migrations/0001_initial.py +++ b/apikey/migrations/0001_initial.py @@ -88,7 +88,7 @@ class Migration(migrations.Migration): models.CharField( db_index=True, editable=False, - max_length=150, + max_length=136, verbose_name="hashed key", ), ), diff --git a/apikey/models.py b/apikey/models.py index b172bb7a..36e20287 100644 --- a/apikey/models.py +++ b/apikey/models.py @@ -17,9 +17,7 @@ class ApiClient(models.Model): on_delete=models.CASCADE, ) groups = models.ManyToManyField( - Group, - verbose_name=_("groups"), - related_name="api_clients", + Group, verbose_name=_("groups"), related_name="api_clients", blank=True ) client_permissions = models.ManyToManyField( Permission, @@ -70,11 +68,13 @@ class ApiClient(models.Model): class ApiKey(models.Model): PREFIX_LENGTH = 5 + KEY_LENGTH = 72 + HASHED_KEY_LENGTH = 136 name = models.CharField(_("name"), blank=True, default="") prefix = models.CharField(_("prefix"), max_length=PREFIX_LENGTH, editable=False) hashed_key = models.CharField( - _("hashed key"), max_length=150, db_index=True, editable=False + _("hashed key"), max_length=HASHED_KEY_LENGTH, db_index=True, editable=False ) client = models.ForeignKey( ApiClient,