diff --git a/src/grung/types.py b/src/grung/types.py index cdba6ec..34e2653 100644 --- a/src/grung/types.py +++ b/src/grung/types.py @@ -1,7 +1,9 @@ from __future__ import annotations import hashlib +import hmac import os +import re from collections import namedtuple from dataclasses import dataclass, field from typing import Dict, List @@ -57,6 +59,21 @@ class Password(Field): salt_size = 4 digest_size = 16 + @classmethod + def is_digest(cls, passwd: str): + if not passwd: + return False + offset = 2 * cls.salt_size # each byte is 2 hex chars + try: + if passwd[offset] != ":": + return False + digest = passwd[offset + 1 :] + if len(digest) != cls.digest_size * 2: + return False + return re.match(r"^[0-9a-f]+$", digest) + except IndexError: + return False + @classmethod def get_digest(cls, passwd: str, salt: bytes = None): if not salt: @@ -68,10 +85,10 @@ class Password(Field): def compare(cls, passwd: value_type, stored: value_type): stored_salt, stored_digest = stored.split(":") input_digest, input_salt = cls.get_digest(passwd, bytes.fromhex(stored_salt)) - return input_digest == stored_digest + return hmac.compare_digest(input_digest, stored_digest) def before_insert(self, value: value_type, db: TinyDB, record: Record) -> None: - if value: + if value and not self.__class__.is_digest(value): digest, salt = self.__class__.get_digest(value) record[self.name] = f"{salt}:{digest}" diff --git a/test/test_db.py b/test/test_db.py index eb06005..ddfc49b 100644 --- a/test/test_db.py +++ b/test/test_db.py @@ -123,6 +123,9 @@ def test_search(db): def test_password(db): user = db.save(examples.User(name="john", email="john@foo", password="fnord")) + # make sure we don't compute the digest on an existing digest + user = db.save(user) + assert ":" in user.password assert user.password != "fnord"