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

24 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 12:42 +0000

1import hashlib 

2import hmac 

3import secrets 

4import string 

5import zlib 

6 

7 

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

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: 

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

20 return hash.hexdigest() 

21 

22 

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

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]: 

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)