crypto.py 1.6 KB

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