Avoid using PyCryptodome for AES-GCM decryption of file contents

This removes all PyCryptodome dependencies
This commit is contained in:
RunasSudo 2024-05-27 22:23:29 +10:00
parent 2af1d5385b
commit 8aa4c8dc48
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A

View File

@ -17,7 +17,10 @@
from .aes import aes_siv_decrypt, aes_siv_encrypt from .aes import aes_siv_decrypt, aes_siv_encrypt
from .b64url import b64url_decode, b64url_encode from .b64url import b64url_decode, b64url_encode
from Crypto.Cipher import AES from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import GCM
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.primitives.keywrap import aes_key_unwrap, InvalidUnwrap from cryptography.hazmat.primitives.keywrap import aes_key_unwrap, InvalidUnwrap
@ -228,8 +231,7 @@ def decrypt_file(vault_path, primary_master_key, hashed_directory_id, filename):
header_tag = ciphertext_header[-16:] header_tag = ciphertext_header[-16:]
# Decrypt header to obtain the content key # Decrypt header to obtain the content key
cipher = AES.new(primary_master_key, AES.MODE_GCM, nonce=header_nonce) plaintext_header = AESGCM(primary_master_key).decrypt(header_nonce, header_payload + header_tag, None)
plaintext_header = cipher.decrypt_and_verify(header_payload, header_tag)
content_key = plaintext_header[8:] content_key = plaintext_header[8:]
# Decrypt file in chunks corresponding to 32 KiB plaintext # Decrypt file in chunks corresponding to 32 KiB plaintext
@ -241,15 +243,13 @@ def decrypt_file(vault_path, primary_master_key, hashed_directory_id, filename):
chunk_payload = ciphertext_chunk[12:-16] chunk_payload = ciphertext_chunk[12:-16]
chunk_tag = ciphertext_chunk[-16:] chunk_tag = ciphertext_chunk[-16:]
# Chunk is encrypted with AES-GCM # Chunk is encrypted with AES-GCM - chunk number and header nonce are passed as AAD
cipher = AES.new(content_key, AES.MODE_GCM, nonce=chunk_nonce) # cryptography.hazmat.primitives.ciphers.aead.AESGCM does not support multiple AAD so must do this manually
cipher = Cipher(AES(content_key), GCM(chunk_nonce, chunk_tag)).decryptor()
cipher.authenticate_additional_data(struct.pack('>Q', chunk_num))
cipher.authenticate_additional_data(header_nonce)
plaintext_chunk = cipher.update(chunk_payload) + cipher.finalize()
# Chunk number and header nonce are passed as AAD
# Contrary to the Cryptomator documentation, chunk number is a 64-bit not 32-bit integer - https://github.com/cryptomator/docs/pull/54
cipher.update(struct.pack('>Q', chunk_num))
cipher.update(header_nonce)
plaintext_chunk = cipher.decrypt_and_verify(chunk_payload, chunk_tag)
plaintext.extend(plaintext_chunk) plaintext.extend(plaintext_chunk)
return bytes(plaintext) return bytes(plaintext)