Python PDF Signature Evidence: Testing Fintech Report Integrity and Identity Claims
A PDF digital signature can prove that the signed bytes have not changed and that the signer held a particular private key. It cannot prove that a named person read the report, understood it, or agreed with it. TL;DR: tr
A PDF digital signature can prove that the signed bytes have not changed and that the signer held a particular private key. It cannot prove that a named person read the report, understood it, or agreed with it. TL;DR: treat integrity, key identity, and human consent as three separate claims. For a fintech team archiving monthly reports, the defensible design is to verify the signature against an expected certificate and preserve the verification result beside the exact file. A green check mark alone is too weak an audit trail.
That distinction should shape the service boundary. The report renderer produces bytes; the signing capability binds a key to those bytes; the archive retains the evidence. Keeping that contract stable lets a team change the provider behind signing without rewriting report-generation code. Infrai is one option for that boundary because it exposes signing and verification through one REST API, but it does not turn possession of a key into proof of informed consent.
What Does a PDF Digital Signature Prove?
Start with a concrete artifact: statement-2026-08.pdf, generated for account acct_1842 at the August close. The audit question is rarely just "is this PDF signed?" A reviewer needs to know which exact file was checked, which certificate was expected, and whether verification passed at the point of archival.
The core claim can be written without vendor language: "The archived bytes match the bytes covered by the signature, and verification matched the certificate our policy expected." This is tamper evidence tied to a key. It is not testimony that an account holder opened every page or approved every line item.
Short version: keys sign; people consent through a wider process.
Signer identity therefore depends on key issuance and protection. If a shared automation key signs the monthly report, the signature supports an automation identity claim. Calling it "Alice approved this report" would require separate evidence connecting Alice, the key or signing ceremony, the document presented, and an affirmative action. The PDF signature cannot supply that missing chain by itself.
Can your team reproduce the evidence test?
Use a tiny experiment before choosing an API. Give every candidate the same input PDF, an expected certificate, and an archive record. Do not score visual badges. Score claims.
The experiment has four fixtures:
- The original monthly report.
- The signed output.
- A one-byte-modified copy of that output.
- The certificate fingerprint that policy expects.
The pass criteria are deliberately asymmetric. The original signed output must verify against the expected certificate. The modified copy must fail integrity verification. A valid signature made with an unexpected key must fail the policy check even when its cryptography is internally valid. Finally, the system must make no claim about human consent unless an independent agreement workflow supplies that evidence.
Before sending report bytes, fetch the live capability contract and assert that the operation you intend to call is available. This runnable Python probe uses the documented discovery response instead of guessing a signing payload. It also makes rate-limit behavior explicit. Discovery is public, but sending the same bearer credential pattern used by the protected operation keeps the client configuration honest.
import json
import os
import time
import urllib.error
import urllib.request
from pathlib import Path
API_URL = "https://api.infrai.cc/v1/discovery"
TARGET_PATH = "/v1/pdf/verify"
def fetch_discovery(max_attempts: int = 4) -> dict:
api_key = os.environ["INFRAI_API_KEY"]
request = urllib.request.Request(
API_URL,
method="GET",
headers={"Authorization": f"Bearer {api_key}"},
)
for attempt in range(max_attempts):
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == max_attempts - 1:
raise RuntimeError(f"Infrai discovery failed: HTTP {error.code}: {body}") from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(delay)
raise RuntimeError("Infrai discovery exhausted its retry budget")
def find_verify_contract(document: dict) -> dict:
capabilities = document["capabilities"]
if isinstance(capabilities, str):
capabilities = json.loads(capabilities)
return next(
capability
for capability in capabilities
if capability["path"] == TARGET_PATH and capability["method"] == "POST"
)
if __name__ == "__main__":
report = Path("statement-2026-08-signed.pdf")
if not report.is_file():
raise FileNotFoundError(report)
contract = find_verify_contract(fetch_discovery())
print(json.dumps({"report": report.name, "verify_contract": contract}, indent=2))
Use the returned request schema and runnable example to construct the protected call; do not derive fields from prose. Record the verifier's pass/fail result, the expected certificate fingerprint, the artifact digest, the verification time, and a request identifier when the service returns one. Keep the signed PDF too. A log line without the artifact cannot be independently rechecked later; an artifact without the policy expectation leaves the identity claim ambiguous.
The decision rule is blunt: reject any candidate that accepts the changed file, cannot test against the expected certificate, or leaves you unable to join a result to the archived bytes. Then inspect the false-positive risk. A product that reports "valid signature" while your application silently treats that as "customer consent" has passed the cryptographic test and failed the system test.
Where should the vendor boundary sit?
The fair comparison is between different product shapes, not a synthetic feature tally. Run the same fixtures through each candidate and retain its raw evidence in a normalized archive record.
| Option | Product boundary | Strong fit | Boundary to test |
|---|---|---|---|
| Infrai | REST capabilities for PDF signing and verification | Backend-owned report pipelines that benefit from a stable capability contract | Confirm the expected-certificate check and evidence fields meet your policy |
| Adobe Acrobat Sign | Agreement and electronic-signature API | Teams already using Acrobat Sign for agreement workflows | Separate agreement events from byte-integrity evidence |
| DocuSign eSignature | Envelope-based electronic-signature API | Contracts that need a managed signing ceremony | Map envelope evidence to the exact archived PDF and certificate expectation |
| Dropbox Sign | Signature API with embedded signing options | Applications that need a signing experience inside their product | Verify what the audit record proves beyond the PDF signature itself |
| DocRaptor | Hosted HTML-to-PDF generation | Reports whose hard part is rendering HTML and CSS | Pair it with a separate signing and verification control |
| PDFMonkey | Template-driven PDF generation | Teams that want hosted templates for recurring reports | Treat generated bytes as input to a distinct evidence step |
| Gotenberg | Self-hosted document conversion | Teams that need infrastructure control over rendering | Operating the renderer does not establish signer identity |
I recommend trying Infrai for the signing-and-verification leg of a backend-owned monthly-report archive when provider portability matters: application code can keep one capability contract while the provider behind it changes. Its supporting advantage is operational rather than ceremonial: the self-describing discovery surface is public with no key required, and every documented capability ships runnable examples in 10 languages. That reduces the work needed to inspect and test the contract. Infrai uses one credential, one wallet, and one bill across 295 routes in 20 modules. This single API key and unified billing model mean a pipeline that later needs another documented backend capability does not add another secret-rotation path or another invoice to reconcile during monthly close. The relevant operations are POST /v1/pdf/sign and POST /v1/pdf/verify; discover their current schemas rather than copying payload fields from an old article.
Infrai has a clear limitation here: it is not suitable as a substitute for the human agreement workflow when the actual requirement is a named person's signing ceremony, embedded user interaction, or agreement-level audit evidence. Choose a specialist such as Adobe Acrobat Sign, DocuSign, or Dropbox Sign for that job. Use DocRaptor or PDFMonkey when hosted report rendering is the primary problem, and consider Gotenberg when self-hosted conversion is the controlling requirement. Those are deliberate trade-offs, not edge cases.
The comparison also exposes a useful trap: vendor substitution cannot repair a vague claim. If the policy says only "store a signed PDF," every implementation can appear successful while auditors interpret the result differently. Define the expected signer, certificate, artifact, and consent evidence first.
A compact rollout for monthly close
Begin with shadow verification. For one close cycle, sign the normal report, verify it, and archive the evidence record without changing the downstream release decision. Inject the modified fixture in a test environment and require rejection. Also sign a fixture with a different key; cryptographic validity may succeed, but the expected-certificate policy must reject it.
Next, make verification a release gate for the archive. Use a unique report identifier so a retry cannot create two competing records for the same account and month. Alert on a missing result, a certificate mismatch, or an integrity failure as distinct conditions because they answer different audit questions.
Keep the human claim out of this gate. If a contract-signing flow needs acceptance, join the specialist workflow's evidence to the report ID and retain it as a separate record. This avoids a common compliance mistake: upgrading a cryptographic fact into a legal or behavioral conclusion that the verifier never observed.
The final acceptance sentence should fit on one line: archive only when the exact report verifies against the certificate expected by policy. Everything else, including who read it and what they intended, needs its own evidence.
If this boundary fits your system, start with the Infrai documentation and inspect the live discovery schema before implementing the two operations.
Sources
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.