Source code for pynamodb_mate.cipher
# -*- coding: utf-8 -*-
"""
implement the abstract cipher.
"""
import json
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from .helpers import bytes_to_base85str, base85str_to_bytes
def str_to_key(s: str) -> bytes:
m = hashlib.sha256()
m.update(s.encode("utf-8"))
return m.digest()
class BaseCipher:
def encrypt(self, b: bytes) -> bytes: # pragma: no cover
raise NotImplementedError
def decrypt(self, b: bytes) -> bytes: # pragma: no cover
raise NotImplementedError
[docs]class AesEcbCipher(BaseCipher):
"""
A determinative symmetric cipher. same input + same key = same output.
"""
def __init__(self, key: bytes):
self._key = key
self._aes = AES.new(self._key, AES.MODE_ECB)
def encrypt(self, b: bytes) -> bytes:
return self._aes.encrypt(pad(b, AES.block_size))
def decrypt(self, b: bytes) -> bytes:
return unpad(self._aes.decrypt(b), AES.block_size)
[docs]class AesCtrCipher(BaseCipher):
"""
A non-determinative symmetric cipher. same input + same key != same output.
"""
def __init__(self, key: bytes):
self._key = key
def encrypt(self, b: bytes) -> bytes:
aes = AES.new(self._key, AES.MODE_CTR)
token = aes.encrypt(b)
nonce = aes.nonce
return json.dumps({
"nonce": bytes_to_base85str(nonce),
"token": bytes_to_base85str(token),
}).encode("ascii")
def decrypt(self, b: bytes) -> bytes:
data = json.loads(b.decode("ascii"))
token = base85str_to_bytes(data["token"])
nonce = base85str_to_bytes(data["nonce"])
aes = AES.new(self._key, AES.MODE_CTR, nonce=nonce)
return aes.decrypt(token)