crypto.py 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. from base64 import urlsafe_b64encode
  2. from cryptography.fernet import Fernet, InvalidToken
  3. from cryptography.hazmat.primitives import hashes
  4. from cryptography.hazmat.primitives.kdf.kbkdf import CounterLocation, KBKDFHMAC, Mode
  5. from cryptography.hazmat.backends import default_backend
  6. from django.conf import settings
  7. from django.utils.encoding import force_bytes
  8. from desecapi import metrics
  9. def _derive_urlsafe_key(*, label, context):
  10. backend = default_backend()
  11. kdf = KBKDFHMAC(
  12. algorithm=hashes.SHA256(),
  13. mode=Mode.CounterMode,
  14. length=32,
  15. rlen=4,
  16. llen=4,
  17. location=CounterLocation.BeforeFixed,
  18. label=label,
  19. context=context,
  20. fixed=None,
  21. backend=backend,
  22. )
  23. key = kdf.derive(settings.SECRET_KEY.encode())
  24. return urlsafe_b64encode(key)
  25. def retrieve_key(*, label, context):
  26. # Keeping this function separate from key derivation gives us freedom to implement look-ups later, e.g. from cache
  27. label = force_bytes(label, strings_only=True)
  28. context = force_bytes(context, strings_only=True)
  29. return _derive_urlsafe_key(label=label, context=context)
  30. def encrypt(data, *, context):
  31. key = retrieve_key(label=b"crypt", context=context)
  32. value = Fernet(key=key).encrypt(data)
  33. metrics.get("desecapi_key_encryption_success").labels(context).inc()
  34. return value
  35. def decrypt(token, *, context, ttl=None):
  36. key = retrieve_key(label=b"crypt", context=context)
  37. f = Fernet(key=key)
  38. try:
  39. ret = f.extract_timestamp(token), f.decrypt(token, ttl=ttl)
  40. metrics.get("desecapi_key_decryption_success").labels(context).inc()
  41. return ret
  42. except InvalidToken:
  43. raise ValueError