@@ -374,45 +374,57 @@ class EncryptedCache(_KeyMutMapMix, _StatsMix, MutableMapping):
374
374
"""Encrypted Bytes Key-Value Cache.
375
375
376
376
:param secret: bytes of secret, at least 16 bytes.
377
- :param hsize: size of hashed key, default is 16.
377
+ :param hsize: size of hashed key, default is *16*.
378
+ :param csize: value checksum size, default is *0*.
378
379
379
380
The key is *not* encrypted but simply hashed, thus they are
380
381
fixed size with a very low collision probability.
381
382
382
383
By design, the clear-text key is needed to recover the value,
383
- as each value is encrypted with its own key.
384
-
385
- There is no integrity check on the value.
384
+ as each value is encrypted with its own key and nonce.
386
385
387
386
Algorithms:
388
- - SHA3: hash/key/nonce derivation.
387
+ - SHA3: hash/key/nonce derivation and checksum .
389
388
- Salsa20: value encryption.
390
389
"""
391
390
392
- def __init__ (self , cache : MutableMapping , secret : bytes , hsize : int = 16 ):
391
+ def __init__ (self , cache : MutableMapping , secret : bytes , hsize : int = 16 , csize : int = 0 ):
393
392
self ._cache = cache
394
393
assert len (secret ) >= 16
395
394
self ._secret = secret
396
- assert 8 <= hsize <= 24
395
+ assert 8 <= hsize <= 32
397
396
self ._hsize = hsize
397
+ assert 0 <= csize <= 32
398
+ self ._csize = csize
398
399
from Crypto .Cipher import Salsa20
399
400
self ._cipher = Salsa20
400
401
401
- def _keydev (self , key ):
402
+ def _keydev (self , key ) -> tuple [bytes , bytes , bytes ]:
403
+ """Compute hash, key and nonce from initial key."""
402
404
hkey = hashlib .sha3_512 (key + self ._secret ).digest ()
403
- sz = self . _hsize
404
- return (hkey [:sz ], hkey [sz : sz + 32 ], hkey [sz + 32 : sz + 40 ])
405
+ # NOTE hash and nonce may overlap, which is not an issue
406
+ return (hkey [:self . _hsize ], hkey [32 : ], hkey [24 : 32 ])
405
407
406
408
def _key (self , key ):
407
409
return self ._keydev (key )[0 ]
408
410
409
411
def __setitem__ (self , key , val ):
410
412
hkey , vkey , vnonce = self ._keydev (key )
411
- self ._cache [hkey ] = self ._cipher .new (key = vkey , nonce = vnonce ).encrypt (val )
413
+ xval = self ._cipher .new (key = vkey , nonce = vnonce ).encrypt (val )
414
+ if self ._csize :
415
+ cs = hashlib .sha3_256 (val ).digest ()[:self ._csize ]
416
+ xval = cs + xval
417
+ self ._cache [hkey ] = xval
412
418
413
419
def __getitem__ (self , key ):
414
420
hkey , vkey , vnonce = self ._keydev (key )
415
- return self ._cipher .new (key = vkey , nonce = vnonce ).decrypt (self ._cache [hkey ])
421
+ xval = self ._cache [hkey ]
422
+ if self ._csize : # split cs from encrypted value
423
+ cs , xval = xval [:self ._csize ], xval [self ._csize :]
424
+ val = self ._cipher .new (key = vkey , nonce = vnonce ).decrypt (xval )
425
+ if self ._csize and cs != hashlib .sha3_256 (val ).digest ()[:self ._csize ]:
426
+ raise KeyError (f"invalid encrypted value for key { key } " )
427
+ return val
416
428
417
429
418
430
class BytesCache (_KeyMutMapMix , _StatsMix , MutableMapping ):
0 commit comments