Coverage for polar/kit/crypto.py: 42%

24 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-12-05 17:15 +0000

1import hashlib 1ab

2import hmac 1ab

3import secrets 1ab

4import string 1ab

5import zlib 1ab

6 

7 

8def _crc32_to_base62(number: int) -> str: 1ab

9 characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 

10 base = len(characters) 

11 encoded = "" 

12 while number: 

13 number, remainder = divmod(number, base) 

14 encoded = characters[remainder] + encoded 

15 return encoded.zfill(6) # Ensure the checksum is 6 characters long 

16 

17 

18def get_token_hash(token: str, *, secret: str) -> str: 1ab

19 hash = hmac.new(secret.encode("ascii"), token.encode("ascii"), hashlib.sha256) 1c

20 return hash.hexdigest() 1c

21 

22 

23def generate_token(*, prefix: str = "") -> str: 1ab

24 # Generate a high entropy random token 

25 token = "".join( 

26 secrets.choice(string.ascii_letters + string.digits) for _ in range(37) 

27 ) 

28 

29 # Calculate a 32-bit CRC checksum 

30 checksum = zlib.crc32(token.encode("utf-8")) & 0xFFFFFFFF 

31 checksum_base62 = _crc32_to_base62(checksum) 

32 

33 # Concatenate the prefix, token, and checksum 

34 return f"{prefix}{token}{checksum_base62}" 

35 

36 

37def generate_token_hash_pair(*, secret: str, prefix: str = "") -> tuple[str, str]: 1ab

38 """ 

39 Generate a token suitable for sensitive values like API tokens. 

40 

41 Returns both the actual value and its HMAC-SHA256 hash. 

42 Only the latter shall be stored in database. 

43 """ 

44 token = generate_token(prefix=prefix) 

45 return token, get_token_hash(token, secret=secret)