#36874: Speed up mask/unmask cipher secret functions
-------------------------------------+-------------------------------------
     Reporter:  Tim Lansen           |                    Owner:  Tim
         Type:                       |  Lansen
  Cleanup/optimization               |                   Status:  assigned
    Component:  CSRF                 |                  Version:  6.0
     Severity:  Normal               |               Resolution:
     Keywords:  CSRF cipher token    |             Triage Stage:
  mask unmask                        |  Unreviewed
    Has patch:  1                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Comment (by Tim Lansen):

 Benchmarking the approach with Python 3.12.7 on ASUS Vivobook (Intel Core
 Ultra 9)
 {{{
 $ python csrf_cipher_benchmark.py
 Execution time 1: 2.173560 (2.7169501781463623e-05 sec per mask+unmask)
 Execution time 2: 1.631568 (2.0394599437713623e-05 sec per mask+unmask)
 }}}
 The code
 {{{
 import secrets


 def get_random_string(length, allowed_chars):
     return "".join(secrets.choice(allowed_chars) for i in range(length))


 CSRF_SECRET_LENGTH = 32
 CSRF_ALLOWED_CHARS =
 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'


 def _get_new_csrf_string():
     return get_random_string(CSRF_SECRET_LENGTH,
 allowed_chars=CSRF_ALLOWED_CHARS)


 def _make_xlat(chars: str):
     xlat = [0 for _ in range(1 + max((ord(x) for x in chars)))]
     for i, c in enumerate(chars):
         xlat[ord(c)] = i
     return xlat


 CSRF_XLAT = _make_xlat(CSRF_ALLOWED_CHARS)


 def _mask_cipher_secret(secret):
     """
     Given a secret (assumed to be a string of CSRF_ALLOWED_CHARS),
 generate a
     token by adding a mask and applying it to the secret.
     """
     mask = _get_new_csrf_string()
     chars = CSRF_ALLOWED_CHARS
     pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in
 mask))
     cipher = "".join(chars[(x + y) % len(chars)] for x, y in pairs)
     return mask + cipher


 def _unmask_cipher_token(token):
     """
     Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
     CSRF_TOKEN_LENGTH, and that its first half is a mask), use it to
 decrypt
     the second half to produce the original secret.
     """
     mask = token[:CSRF_SECRET_LENGTH]
     token = token[CSRF_SECRET_LENGTH:]
     xlat = CSRF_XLAT
     chars = CSRF_ALLOWED_CHARS
     pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in
 mask))
     return "".join(chars[x - y] for x, y in pairs)  # Note negative values
 are ok


 def _mask_cipher_secret_xlat(secret):
     """
     Given a secret (assumed to be a string of CSRF_ALLOWED_CHARS),
 generate a
     token by adding a mask and applying it to the secret.
     """
     mask = _get_new_csrf_string()
     chars = CSRF_ALLOWED_CHARS
     pairs = zip((CSRF_XLAT[ord(x)] for x in secret), (CSRF_XLAT[ord(x)]
 for x in mask))
     cipher = "".join(chars[(x + y) % len(chars)] for x, y in pairs)
     return mask + cipher


 def _unmask_cipher_token_xlat(token):
     """
     Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
     CSRF_TOKEN_LENGTH, and that its first half is a mask), use it to
 decrypt
     the second half to produce the original secret.
     """
     mask = token[:CSRF_SECRET_LENGTH]
     token = token[CSRF_SECRET_LENGTH:]
     chars = CSRF_ALLOWED_CHARS
     pairs = zip((CSRF_XLAT[ord(x)] for x in token), (CSRF_XLAT[ord(x)] for
 x in mask))
     return "".join(chars[x - y] for x, y in pairs)  # Note negative values
 are ok


 def benchmark(secrets: int, iterations: int):
     d1, d2 = 0.0, 0.0
     for i in range(secrets):
         secret = _get_new_csrf_string()
         import time
         t0 = time.time()
         for _ in range(iterations):
             token = _mask_cipher_secret(secret)
             secret = _unmask_cipher_token(token)
         t1 = time.time()
         for _ in range(iterations):
             token = _mask_cipher_secret_xlat(secret)
             secret = _unmask_cipher_token_xlat(token)
         t2 = time.time()
         d1 += t1 - t0
         d2 += t2 - t1
     print(f'Execution time 1: {d1:.6f} ({d1 / secrets / iterations} sec
 per mask+unmask)')
     print(f'Execution time 2: {d2:.6f} ({d2 / secrets / iterations} sec
 per mask+unmask)')


 if __name__ == '__main__':
     benchmark(200, 400)
 }}}
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36874#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019be0d6effd-84ea763f-0b0b-48fa-bfd0-e262b22cab6c-000000%40eu-central-1.amazonses.com.

Reply via email to