diff --git a/.github/workflows/github-ci.yaml b/.github/workflows/github-ci.yaml index 576c64e6f..734b8113b 100644 --- a/.github/workflows/github-ci.yaml +++ b/.github/workflows/github-ci.yaml @@ -22,7 +22,10 @@ jobs: strategy: matrix: python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] - + use-cryptodome: [""] + include: + - python-version: "3.10" + use-cryptodome: "false" steps: - name: Checkout Code uses: actions/checkout@v3 @@ -38,6 +41,10 @@ jobs: - name: Install requirements (Python 3) run: | pip install -r requirements/ci.txt + - name: Remove cryptodome + run: | + pip uninstall pycryptodome -y + if: matrix.use-cryptodome == false - name: Install PyPDF2 run: | pip install . diff --git a/PyPDF2/_encryption.py b/PyPDF2/_encryption.py index 621cc2d52..4327baf63 100644 --- a/PyPDF2/_encryption.py +++ b/PyPDF2/_encryption.py @@ -56,7 +56,7 @@ class CryptIdentity(CryptBase): try: - from Crypto.Cipher import AES, ARC4 + from Crypto.Cipher import AES, ARC4 # type: ignore[import] class CryptRC4(CryptBase): def __init__(self, key: bytes) -> None: diff --git a/tests/test_encryption.py b/tests/test_encryption.py index c357fc237..43ebdf20e 100644 --- a/tests/test_encryption.py +++ b/tests/test_encryption.py @@ -3,6 +3,14 @@ import pytest import PyPDF2 +from PyPDF2.errors import DependencyError + +try: + from Crypto.Cipher import AES # noqa: F401 + + HAS_PYCRYPTODOME = True +except ImportError: + HAS_PYCRYPTODOME = False TESTS_ROOT = os.path.abspath(os.path.dirname(__file__)) PROJECT_ROOT = os.path.dirname(TESTS_ROOT) @@ -10,46 +18,54 @@ @pytest.mark.parametrize( - "name", + ("name", "requres_pycryptodome"), [ # unencrypted pdf - "unencrypted.pdf", + ("unencrypted.pdf", False), # created by `qpdf --encrypt "" "" 40 -- unencrypted.pdf r2-empty-password.pdf` - "r2-empty-password.pdf", + ("r2-empty-password.pdf", False), # created by `qpdf --encrypt "" "" 128 -- unencrypted.pdf r3-empty-password.pdf` - "r3-empty-password.pdf", + ("r3-empty-password.pdf", False), # created by `qpdf --encrypt "asdfzxcv" "" 40 -- unencrypted.pdf r2-user-password.pdf` - "r2-user-password.pdf", + ("r2-user-password.pdf", False), # created by `qpdf --encrypt "asdfzxcv" "" 128 -- unencrypted.pdf r3-user-password.pdf` - "r3-user-password.pdf", + ("r3-user-password.pdf", False), # created by `qpdf --encrypt "asdfzxcv" "" 128 --force-V4 -- unencrypted.pdf r4-user-password.pdf` - "r4-user-password.pdf", + ("r4-user-password.pdf", False), # created by `qpdf --encrypt "asdfzxcv" "" 128 --use-aes=y -- unencrypted.pdf r4-aes-user-password.pdf` - "r4-aes-user-password.pdf", + ("r4-aes-user-password.pdf", True), # # created by `qpdf --encrypt "" "" 256 --force-R5 -- unencrypted.pdf r5-empty-password.pdf` - "r5-empty-password.pdf", + ("r5-empty-password.pdf", True), # # created by `qpdf --encrypt "asdfzxcv" "" 256 --force-R5 -- unencrypted.pdf r5-user-password.pdf` - "r5-user-password.pdf", + ("r5-user-password.pdf", True), # # created by `qpdf --encrypt "" "asdfzxcv" 256 --force-R5 -- unencrypted.pdf r5-owner-password.pdf` - "r5-owner-password.pdf", + ("r5-owner-password.pdf", True), # created by `qpdf --encrypt "" "" 256 -- unencrypted.pdf r6-empty-password.pdf` - "r6-empty-password.pdf", + ("r6-empty-password.pdf", True), # created by `qpdf --encrypt "asdfzxcv" "" 256 -- unencrypted.pdf r6-user-password.pdf` - "r6-user-password.pdf", + ("r6-user-password.pdf", True), # created by `qpdf --encrypt "" "asdfzxcv" 256 -- unencrypted.pdf r6-owner-password.pdf` - "r6-owner-password.pdf", + ("r6-owner-password.pdf", True), ], ) -def test_encryption(name): +def test_encryption(name, requres_pycryptodome): inputfile = os.path.join(RESOURCE_ROOT, "encryption", name) - ipdf = PyPDF2.PdfReader(inputfile) - if inputfile.endswith("unencrypted.pdf"): - assert not ipdf.is_encrypted + if requres_pycryptodome and not HAS_PYCRYPTODOME: + with pytest.raises(DependencyError) as exc: + ipdf = PyPDF2.PdfReader(inputfile) + ipdf.decrypt("asdfzxcv") + dd = dict(ipdf.metadata) + assert exc.value.args[0] == "PyCryptodome is required for AES algorithm" + return else: - assert ipdf.is_encrypted - ipdf.decrypt("asdfzxcv") - assert len(ipdf.pages) == 1 - dd = dict(ipdf.metadata) + ipdf = PyPDF2.PdfReader(inputfile) + if inputfile.endswith("unencrypted.pdf"): + assert not ipdf.is_encrypted + else: + assert ipdf.is_encrypted + ipdf.decrypt("asdfzxcv") + assert len(ipdf.pages) == 1 + dd = dict(ipdf.metadata) # remove empty value entry dd = {x[0]: x[1] for x in dd.items() if x[1]} assert dd == { @@ -69,6 +85,7 @@ def test_encryption(name): ("r6-both-passwords.pdf", "foo", "bar"), ], ) +@pytest.mark.skipif(not HAS_PYCRYPTODOME, reason="No pycryptodome") def test_both_password(name, user_passwd, owner_passwd): from PyPDF2 import PasswordType @@ -80,6 +97,24 @@ def test_both_password(name, user_passwd, owner_passwd): assert len(ipdf.pages) == 1 +@pytest.mark.parametrize( + ("pdffile", "password"), + [ + ("crazyones-encrypted-256.pdf", "password"), + ], +) +@pytest.mark.skipif(not HAS_PYCRYPTODOME, reason="No pycryptodome") +def test_get_page_of_encrypted_file_new_algorithm(pdffile, password): + """ + Check if we can read a page of an encrypted file. + + This is a regression test for issue 327: + IndexError for get_page() of decrypted file + """ + path = os.path.join(RESOURCE_ROOT, pdffile) + PyPDF2.PdfReader(path, password=password).pages[0] + + @pytest.mark.parametrize( "names", [ @@ -93,6 +128,7 @@ def test_both_password(name, user_passwd, owner_passwd): ), ], ) +@pytest.mark.skipif(not HAS_PYCRYPTODOME, reason="No pycryptodome") def test_encryption_merge(names): pdf_merger = PyPDF2.PdfMerger() files = [os.path.join(RESOURCE_ROOT, "encryption", x) for x in names] diff --git a/tests/test_reader.py b/tests/test_reader.py index 49ec3e79f..6018a196c 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -280,23 +280,6 @@ def test_get_page_of_encrypted_file(pdffile, password, should_fail): PdfReader(path, password=password).pages[0] -@pytest.mark.parametrize( - ("pdffile", "password"), - [ - ("crazyones-encrypted-256.pdf", "password"), - ], -) -def test_get_page_of_encrypted_file_new_algorithm(pdffile, password): - """ - Check if we can read a page of an encrypted file. - - This is a regression test for issue 327: - IndexError for get_page() of decrypted file - """ - path = os.path.join(RESOURCE_ROOT, pdffile) - PdfReader(path, password=password).pages[0] - - @pytest.mark.parametrize( ("src", "expected", "expected_get_fields"), [