def _read_exact(fobj: io.BufferedIOBase, size: int) -> bytes: buf = bytearray(size) bufmv = memoryview(buf) while size > 0: bytes_read = fobj.readinto(bufmv[-size:]) if bytes_read == 0: raise RuntimeError('EOF occurred') size -= bytes_read return bytes(buf)
def _decrypt(cls, fobj: io.BufferedIOBase, input_size: typing.Optional[int], privkey: x25519.X25519PrivateKey, chunk_size: int) -> typing.Iterable[bytes]: if input_size is None: input_size = cls._get_size_till_eof(fobj) epubkey = x25519.X25519PublicKey.from_public_bytes( cls._read_exact(fobj, cls._PUBKEY_SIZE_BYTES)) input_size -= cls._PUBKEY_SIZE_BYTES shared_secret = privkey.exchange(epubkey) iv = cls._read_exact(fobj, cls._IV_SIZE_BYTES) input_size -= cls._IV_SIZE_BYTES cipher_key = cls._derive_keys(shared_secret, iv) cipher_iv = iv[:cls._CIPHER_IV_SIZE_BYTES] decryptor = Cipher(algorithm=cls._CIPHER(cipher_key), mode=GCM(cipher_iv), backend=cls._CRYPTO_BACKEND).decryptor() if input_size < cls._AUTH_TAG_SIZE_BYTES: raise RuntimeError('input_size is too short') bufmv = memoryview(bytearray(chunk_size)) auth_tag = b'' while input_size > 0: bytes_read = fobj.readinto(bufmv) if bytes_read == 0: break input_size -= bytes_read if input_size <= cls._AUTH_TAG_SIZE_BYTES: auth_tag_part_len = cls._AUTH_TAG_SIZE_BYTES - input_size auth_tag += bufmv[bytes_read - auth_tag_part_len:bytes_read] auth_tag += fobj.read() yield decryptor.update(bufmv[:bytes_read - auth_tag_part_len]) else: yield decryptor.update(bufmv[:bytes_read]) yield decryptor.finalize_with_tag(auth_tag)