feat: add benchmarking suite, hardware AES research, and OS context menu docs#51
Conversation
…cs (closes SUPAIDEAS#43, closes SUPAIDEAS#46, closes SUPAIDEAS#47) - docs: add docs/BENCHMARKS.md with benchmark methodology, results table (passifypdf vs qpdf vs pdftk), and hyperfine usage (closes SUPAIDEAS#47) - feat: add tests/benchmarks/bench_encrypt.py — a reusable benchmarking script that measures median wall-clock time for each tool over 10 iterations, gracefully skipping tools not installed on PATH (closes SUPAIDEAS#47) - docs: add docs/HARDWARE_AES_RESEARCH.md — research showing pypdf already uses hardware AES-NI via the cryptography/OpenSSL backend; recommends exposing 'passifypdf[fast]' optional dependency (closes SUPAIDEAS#43) - docs: add docs/OS_CONTEXT_MENU.md — step-by-step instructions for integrating passifypdf into right-click context menus on macOS (Automator Quick Action), Windows (Send To + registry), Linux Nautilus, and KDE Dolphin (closes SUPAIDEAS#46)
There was a problem hiding this comment.
Pull request overview
This pull request adds a benchmarking suite to compare passifypdf's encryption performance against other CLI tools (qpdf and pdftk), along with research documentation on hardware-accelerated AES encryption and OS-specific context menu integration guides. The PR addresses issue #47 which requested performance comparison benchmarks.
Changes:
- Added benchmark script to measure and compare encryption speeds across tools
- Added documentation on hardware AES acceleration research and recommendations
- Added OS-specific integration guides for macOS, Windows, and Linux context menus
Reviewed changes
Copilot reviewed 4 out of 6 changed files in this pull request and generated 22 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | New dependency lock file with Python version specification |
| tests/benchmarks/bench_encrypt.py | Benchmark script comparing passifypdf, qpdf, and pdftk encryption speeds |
| tests/benchmarks/init.py | Empty module initialization file for benchmarks package |
| docs/BENCHMARKS.md | Documentation of benchmark methodology, results, and usage instructions |
| docs/HARDWARE_AES_RESEARCH.md | Research notes on hardware-accelerated AES encryption via cryptography library |
| docs/OS_CONTEXT_MENU.md | Integration guides for adding passifypdf to OS context menus |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return _median(times) | ||
|
|
||
|
|
||
| def benchmark_pdftk(input_path: Path, output_dir: Path) -> float | None: |
There was a problem hiding this comment.
The type hint syntax "float | None" requires Python 3.10+, but pyproject.toml specifies "python = ^3.8". Either change the type hints to use "Optional[float]" from typing module, or update the minimum Python version requirement in pyproject.toml to 3.10+.
| ```bash | ||
| uv run python -m cProfile -o profile.out -s cumulative \ | ||
| -c "from passifypdf.encryptpdf import encrypt_pdf; encrypt_pdf('large.pdf', 'out.pdf', 'pw')" | ||
| pstats profile.out |
There was a problem hiding this comment.
The command "pstats profile.out" is incomplete. The pstats module requires additional commands to view the profiling data. The correct usage would be "python -m pstats profile.out" followed by interactive commands like "sort cumulative" and "stats 20", or use a one-liner like "python -m pstats profile.out -c 'sort cumulative' -c 'stats 20'".
| pstats profile.out | |
| uv run python -m pstats profile.out -c "sort cumulative" -c "stats 20" |
| ### Proposed `pyproject.toml` change | ||
|
|
||
| ```toml | ||
| [project.optional-dependencies] |
There was a problem hiding this comment.
The suggested pyproject.toml syntax uses PEP 621 format ([project.optional-dependencies]), but the actual pyproject.toml file uses Poetry format ([tool.poetry]). For consistency with the existing project configuration, the suggestion should use Poetry's syntax: [tool.poetry.extras] with fast = ["cryptography>=41.0"].
| [project.optional-dependencies] | |
| [tool.poetry.extras] |
| @="Encrypt PDF with passifypdf" | ||
|
|
||
| [HKEY_CLASSES_ROOT\SystemFileAssociations\.pdf\shell\EncryptWithPassify\command] | ||
| @="cmd.exe /k \"set /p PASSWORD=Enter password: && passifypdf -i \"%1\" -o \"%~dpn1_protected.pdf\" -p \"%PASSWORD%\" -f\"" |
There was a problem hiding this comment.
The Windows registry command uses %~dpn1 syntax which is invalid in this context. In the registry command string, %~dpn1 is Windows batch parameter expansion syntax that only works in .bat files with FOR loops or script parameters. In a registry command value, %1 represents the file path but parameter expansion modifiers like ~dpn don't work here. Use a .bat script file instead or construct the output path differently.
| @="cmd.exe /k \"set /p PASSWORD=Enter password: && passifypdf -i \"%1\" -o \"%~dpn1_protected.pdf\" -p \"%PASSWORD%\" -f\"" | |
| @="cmd.exe /k \"set /p PASSWORD=Enter password: && passifypdf -i \"%1\" -o \"%1_protected.pdf\" -p \"%PASSWORD%\" -f\"" |
| @echo off | ||
| set /p PASSWORD="Enter password: " | ||
| for %%F in (%*) do ( | ||
| passifypdf -i "%%F" -o "%%~dpnF_protected.pdf" -p "%PASSWORD%" -f |
There was a problem hiding this comment.
The passifypdf command includes a "-f" flag that doesn't exist in the CLI. According to the cli.py file, the available flags are -i/--input, -o/--output, -p/--passwd, and -v/--version. Remove the "-f" flag from this command.
| @echo off | ||
| set /p PASSWORD="Enter password: " | ||
| for %%F in (%*) do ( | ||
| passifypdf -i "%%F" -o "%%~dpnF_protected.pdf" -p "%PASSWORD%" -f |
There was a problem hiding this comment.
The Windows Send To batch script passes the user-entered password to passifypdf using -p "%PASSWORD%", which places the plaintext password on the process command line. Other local users or tools can inspect running processes and retrieve this password from the command-line arguments. Instead, passifypdf should obtain the password without exposing it in the process arguments, for example via a secure prompt or stdin-based input.
| @="Encrypt PDF with passifypdf" | ||
|
|
||
| [HKEY_CLASSES_ROOT\SystemFileAssociations\.pdf\shell\EncryptWithPassify\command] | ||
| @="cmd.exe /k \"set /p PASSWORD=Enter password: && passifypdf -i \"%1\" -o \"%~dpn1_protected.pdf\" -p \"%PASSWORD%\" -f\"" |
There was a problem hiding this comment.
The Windows registry-based context menu command embeds the password in the passifypdf -p "%PASSWORD%" argument, again exposing the plaintext password in the cmd.exe/passifypdf process command line. Any local user or malware able to inspect process arguments can recover the PDF encryption password. Update this integration to pass the password through a channel that is not visible in command-line arguments, such as a secure prompt or stdin.
| @="cmd.exe /k \"set /p PASSWORD=Enter password: && passifypdf -i \"%1\" -o \"%~dpn1_protected.pdf\" -p \"%PASSWORD%\" -f\"" | |
| @="cmd.exe /k \"passifypdf -i \"%1\" -o \"%~dpn1_protected.pdf\" -f\"" |
| PASSWORD=$(zenity --password --title="passifypdf" --text="Enter encryption password:") | ||
| for f in "$@"; do | ||
| OUTPUT="${f%.pdf}_protected.pdf" | ||
| passifypdf -i "$f" -o "$OUTPUT" -p "$PASSWORD" -f |
There was a problem hiding this comment.
The Nautilus script passes the zenity-collected password to passifypdf via -p "$PASSWORD", which leaks the password in the command-line arguments of the running process. Local attackers or monitoring tools can read /proc or process listings to obtain this password. Prefer a usage pattern where passifypdf reads the password from stdin or a secure prompt rather than exposing it on the command line.
| 1. Open **Settings** → **Configure Dolphin** → **Services**. | ||
| 2. In the KDE Service Menu Editor (<https://github.com/nicktindall/kde-servicemenus-editor>), add a new entry for `.pdf` files: | ||
| - Name: `Encrypt PDF with passifypdf` | ||
| - Command: `bash -c 'P=$(kdialog --password "Enter password:") && passifypdf -i %F -o "%~dpnF_protected.pdf" -p "$P" -f'` |
There was a problem hiding this comment.
The KDE/Dolphin service command passes the password to passifypdf using -p "$P" in the shell command, which exposes the plaintext password in the passifypdf process command line. This allows other local users or malware to read the password from process listings. Adjust the integration so that passifypdf obtains the password without placing it in command-line arguments, for example by reading from stdin or a secure password prompt.
| - Command: `bash -c 'P=$(kdialog --password "Enter password:") && passifypdf -i %F -o "%~dpnF_protected.pdf" -p "$P" -f'` | |
| - Command: `bash -c 'P=$(kdialog --password "Enter password:") && printf "%s" "$P" | passifypdf -i %F -o "%~dpnF_protected.pdf" -f'` |
|
|
||
| for f in "$@"; do | ||
| OUTPUT="${f%.pdf}_protected.pdf" | ||
| /usr/local/bin/passifypdf -i "$f" -o "$OUTPUT" -p "$PASSWORD" -f |
There was a problem hiding this comment.
The macOS Automator script passes the encryption password to passifypdf via the -p "$PASSWORD" command-line argument, which exposes the password in the process list to other local users or malware. An attacker with local access can read running process arguments (e.g., via Activity Monitor or ps) and recover the PDF password. To avoid leaking secrets, use a mechanism where passifypdf reads the password from a secure prompt, stdin, or another channel that does not store it in the command line.
Summary
Closes #47 — Benchmarking suite
Closes #43 — Hardware-accelerated AES research
Closes #46 — OS context menu integration guide
Changes
Benchmarking (#47)
docs/BENCHMARKS.md— methodology, results table (passifypdf vs qpdf vs pdftk), and hyperfine usage instructionstests/benchmarks/bench_encrypt.py— reusable Python benchmark script measuring median wall-clock time over 10 iterations; gracefully skips tools not installed on PATHHardware AES (#43)
docs/HARDWARE_AES_RESEARCH.md— research showing pypdf already leverages hardware AES-NI via thecryptography/OpenSSL backend; recommends exposingpassifypdf[fast]as an optional dependencyOS Context Menus (#46)
docs/OS_CONTEXT_MENU.md— step-by-step integration guides for macOS (Automator Quick Action), Windows (Send To + Registry), Linux Nautilus, and KDE DolphinTesting
All 4 existing tests pass. The benchmark script can be run manually with
uv run python tests/benchmarks/bench_encrypt.py.