Coverage for /Users/davegaeddert/Development/dropseed/plain/plain/plain/utils/crypto.py: 70%

1"""

2 Plain's standard crypto functions and utilities.

3"""

4 import hashlib

5 import hmac

6 import secrets

7

8 from plain.runtime import settings

9 from plain.utils.encoding import force_bytes

10

11

12 class InvalidAlgorithm(ValueError):

13 """Algorithm is not supported by hashlib."""

14

15 pass

16

17

18 def salted_hmac(key_salt, value, secret=None, *, algorithm="sha1"):

19 """

20 Return the HMAC of 'value', using a key generated from key_salt and a

21 secret (which defaults to settings.SECRET_KEY). Default algorithm is SHA1,

22 but any algorithm name supported by hashlib can be passed.

23

24 A different key_salt should be passed in for every application of HMAC.

25 """

26 if secret is None:

27 secret = settings.SECRET_KEY

28

29 key_salt = force_bytes(key_salt)

30 secret = force_bytes(secret)

31 try:

32 hasher = getattr(hashlib, algorithm)

33 except AttributeError as e:

34 raise InvalidAlgorithm(

35 "%r is not an algorithm accepted by the hashlib module." % algorithm

36 ) from e

37 # We need to generate a derived key from our base key. We can do this by

38 # passing the key_salt and our base key through a pseudo-random function.

39 key = hasher(key_salt + secret).digest()

40 # If len(key_salt + secret) > block size of the hash algorithm, the above

41 # line is redundant and could be replaced by key = key_salt + secret, since

42 # the hmac module does the same thing for keys longer than the block size.

43 # However, we need to ensure that we *always* do this.

44 return hmac.new(key, msg=force_bytes(value), digestmod=hasher)

45

46

47 RANDOM_STRING_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

48

49

50 def get_random_string(length, allowed_chars=RANDOM_STRING_CHARS):

51 """

52 Return a securely generated random string.

53

54 The bit length of the returned value can be calculated with the formula:

55 log_2(len(allowed_chars)^length)

56

57 For example, with default `allowed_chars` (26+26+10), this gives:

58 * length: 12, bit length =~ 71 bits

59 * length: 22, bit length =~ 131 bits

60 """

61 return "".join(secrets.choice(allowed_chars) for i in range(length))

62

63

64 def constant_time_compare(val1, val2):

65 """Return True if the two strings are equal, False otherwise."""

66 return secrets.compare_digest(force_bytes(val1), force_bytes(val2))

67

68

69 def pbkdf2(password, salt, iterations, dklen=0, digest=None):

70 """Return the hash of password using pbkdf2."""

71 if digest is None:

72 digest = hashlib.sha256

73 dklen = dklen or None

74 password = force_bytes(password)

75 salt = force_bytes(salt)

76 return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen)