Skip to content

Fix #1585: handle /P permissions written as unsigned int32 in encrypted PDFs#1592

Open
andreasrosdalw wants to merge 1 commit into
LibrePDF:masterfrom
andreasrosdalw:fix-1585-unsigned-permissions
Open

Fix #1585: handle /P permissions written as unsigned int32 in encrypted PDFs#1592
andreasrosdalw wants to merge 1 commit into
LibrePDF:masterfrom
andreasrosdalw:fix-1585-unsigned-permissions

Conversation

@andreasrosdalw

@andreasrosdalw andreasrosdalw commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #1585.

PDFs encrypted with RC4 40-bit (R=2, V=1) by producers like mPDF threw BadPasswordException: Bad user password even with an empty user password, while Acrobat, qpdf, PDFBox and Evince all open the same files fine.

Root cause

mPDF writes the /P permissions entry as an unsigned 32-bit integer (e.g. 4294963412) instead of the signed value required by ISO 32000 (-3884) — the bit pattern is identical. PdfNumber stores values as double, and intValue() did a direct (int) value cast. Per JLS §5.1.3, a double-to-int cast clamps values above Integer.MAX_VALUE to 2147483647 rather than wrapping, so the pValue fed into the MD5 that derives the RC4 key was wrong and no longer matched the /U entry.

Fix

// before
return (int) value;
// after — through long, preserving two's complement semantics
return (int) (long) value;

4294963412 now converts to -3884 as other readers do. All values in the signed 32-bit range are unaffected (including fractional truncation semantics).

Tests

New PdfNumberTest:

  • intValueWrapsUnsignedInt32 — the mPDF value 4294963412 → -3884, plus 4294967292 → -4 and 2147483648 → Integer.MIN_VALUE. Fails on master, passes with the fix.
  • intValueUnchangedForSignedRange — guards existing behavior for normal, boundary and fractional values.

Full openpdf-core suite passes: 2086 tests, 0 failures — confirming nothing relied on the old clamping behavior.

PDFs encrypted with RC4 40-bit by producers like mPDF store the /P
permissions entry as an unsigned 32-bit integer (e.g. 4294963412)
instead of the signed value required by ISO 32000 (-3884). PdfNumber
stores values as double, and the direct double-to-int cast in
intValue() clamps values above Integer.MAX_VALUE to Integer.MAX_VALUE
(JLS 5.1.3) instead of preserving the bit pattern. The wrong /P value
was fed into the MD5 that derives the RC4 key, so PdfReader threw
BadPasswordException even though the user password was empty and every
other reader (Acrobat, qpdf, PDFBox, Evince) opens the file.

Convert through long to get two's complement wrapping, matching how
other readers interpret out-of-range integers. Values in the signed
32-bit range are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BadPasswordException for RC4 40-bit encrypted PDFs with /P stored as unsigned int32

1 participant