Source code for pyflocker.ciphers.backends.cryptography_.ChaCha20

"""ChaCha20 and ChaCha20Poly1305 cipher implementation classes."""

from __future__ import annotations

import typing

from cryptography import exceptions as bkx
from cryptography.hazmat.backends import default_backend as defb
from cryptography.hazmat.primitives.ciphers import (
    Cipher,
    algorithms as algo,
)
from cryptography.hazmat.primitives.poly1305 import Poly1305

from pyflocker.ciphers import base, exc
from pyflocker.ciphers.backends.symmetric import (
    FileCipherWrapper,
    _DecryptionCtx,
    _EncryptionCtx,
)

from .misc import derive_poly1305_key
from .symmetric import NonAEADCipherTemplate

if typing.TYPE_CHECKING:
    import io


[docs] class ChaCha20Poly1305(base.BaseAEADCipher): """ChaCha20Poly1305 Cipher class.""" def __init__(self, encrypting: bool, key: bytes, nonce: bytes) -> None: if len(nonce) not in (8, 12): msg = "A 8 or 12 byte nonce is required" raise ValueError(msg) if len(nonce) == 8: nonce = bytes(4) + nonce cipher = Cipher( algo.ChaCha20(key, (1).to_bytes(4, "little") + nonce), None, ) ctx = cipher.encryptor() if encrypting else cipher.decryptor() self._encrypting = encrypting self._auth = Poly1305(derive_poly1305_key(key, nonce)) self._ctx: typing.Any = self._get_auth_ctx(encrypting, ctx, self._auth) self._len_aad, self._len_ct = 0, 0 self._updated = False self._tag: bytes | None = None @staticmethod def _get_auth_ctx( encrypting: bool, ctx: typing.Any, auth: typing.Any, ) -> _EncryptionCtx | _DecryptionCtx: if encrypting: return _EncryptionCtx(ctx, auth, 0) return _DecryptionCtx(ctx, auth) def _pad_aad(self) -> None: if not self._updated and self._len_aad & 0x0F: self._auth.update(bytes(16 - (self._len_aad & 0x0F))) self._updated = True
[docs] def is_encrypting(self) -> bool: return self._encrypting
[docs] def authenticate(self, data: bytes) -> None: if self._ctx is None: raise exc.AlreadyFinalized if self._updated: raise TypeError self._len_aad += len(data) self._auth.update(data)
[docs] def update(self, data: bytes) -> bytes: if self._ctx is None: raise exc.AlreadyFinalized self._pad_aad() self._len_ct += len(data) return self._ctx.update(data)
[docs] def update_into( self, data: bytes, out: memoryview | bytearray, ) -> None: if self._ctx is None: raise exc.AlreadyFinalized self._pad_aad() self._len_ct += len(out) self._ctx.update_into(data, out)
[docs] def finalize(self, tag: bytes | None = None) -> None: if self._ctx is None: raise exc.AlreadyFinalized if not self.is_encrypting() and tag is None: msg = "tag is required for decryption" raise ValueError(msg) self._pad_aad() if self._len_ct & 0x0F: self._auth.update(bytes(16 - (self._len_ct & 0x0F))) self._auth.update(self._len_aad.to_bytes(8, "little")) self._auth.update(self._len_ct.to_bytes(8, "little")) self._ctx = None if not self.is_encrypting(): assert tag is not None try: self._auth.verify(tag) except bkx.InvalidSignature as e: raise exc.DecryptionError from e else: self._tag = self._auth.finalize()
[docs] def calculate_tag(self) -> bytes | None: if self._ctx is not None: raise exc.NotFinalized if self.is_encrypting(): return self._tag return None
[docs] class ChaCha20(NonAEADCipherTemplate): """ChaCha20 Cipher class. This class alone does not provide any authentication. For AEAD purposes, wrap ``ChaCha20`` object with a class that implements ``BaseAEADCipher`` or use ``ChaCha20Poly1305``. """ def __init__(self, encrypting: bool, key: bytes, nonce: bytes) -> None: if len(nonce) not in (8, 12): msg = "A 8 or 12 byte nonce is required" raise ValueError(msg) if len(nonce) == 8: nonce = bytes(4) + nonce cipher = Cipher( algo.ChaCha20( key, bytes(4) + nonce, ), None, defb(), ) self._ctx = cipher.encryptor() if encrypting else cipher.decryptor() self._encrypting = encrypting
[docs] def new( encrypting: bool, key: bytes, nonce: bytes, *, use_poly1305: bool = True, file: io.BufferedIOBase | None = None, ) -> ChaCha20 | ChaCha20Poly1305 | FileCipherWrapper: """Instantiate a new ChaCha20(-Poly1305) cipher object. Args: encrypting: True is encryption and False is decryption. key: The key for the cipher. nonce: The Nonce for the cipher. It must not be repeated with the same key. Keyword Arguments: use_poly1305: Whether to use Poly1305 MAC with ChaCha20 cipher. file: The source file to read from. Returns: ChaCha20(-Poly1305) cipher wrapper object. Note: Any other error that is raised is from the backend itself. """ crp: typing.Any if file is not None: use_poly1305 = True crp = ( ChaCha20Poly1305(encrypting, key, nonce) if use_poly1305 else ChaCha20(encrypting, key, nonce) ) if file: crp = FileCipherWrapper(crp, file, offset=0) return crp