Skip to content

Encryption support #242

Open
Open

Description

@PhilterPaper

In ssimms/pdfapi2/issues/96, @michal-josef-spacek referred to a new PR he created, ssimms/pdfapi2/pull/95, to handle some aspects of encrypted PDFs. See also ssimms/pdfapi2/issues/48, which was a rejected request for encryption support.

I have created this issue to keep track of this new feature. I hope to get to it within the next few months to look at it and perhaps incorporate it into Builder. Encryption has been on the TODO list for a while (see Road Map item 24). I need to see if this PR at the least fails gracefully when encountering an encrypted PDF, if not allow a password.

Activity

  1. michal-josef-spacek commented on Sep 22, 2026

    @michal-josef-spacek

    I added the PR: #243

  2. michal-josef-spacek commented on Sep 22, 2026

    @michal-josef-spacek

    @PhilterPaper
    First commit is about right error message. I tested on R2, R3, R4-AES, R4-RC4, R5 and R6 PDFs. I have prepared test PDFs.
    Second commit is about adding of R3 example to inform about error.
    Third commit is about first decryption support for R2 test PDF.

    I am working on first version of encryption to R2.

    If you are ok, I could continue wit activity. Any feedback is welcome.

  3. PhilterPaper commented on Sep 22, 2026

    @PhilterPaper
    OwnerAuthor

    Note to developer (self): Desktop/Encryption 242 contains a snapshot of new and changed API2 files, just in case something happens to the original.

    Michal Josef -- before making any PR for PDF::Builder, please discuss (in this ticket) with me just what this new feature does, what any limitations and known bugs are, and what any future plans you have for new encryption methods, etc. As your PR stands right now, it looks like it will be fairly simple for me to incorporate the changes you made into this product. I just want to get a better understanding of what it does before committing to it. Does it cover API2 ticket 48, which was rejected some years ago? Does it at least give a clean error if one tries to open an encrypted PDF?

    Update I was just about to send off this ticket update when your #243 appeared! Talk about fast! Thank you, and let me see if you answered all my questions before I pull in this PR.

  4. michal-josef-spacek commented on Sep 22, 2026

    @michal-josef-spacek

    The original situation of PDF::Builder was, that open() prints error like:

    Objind 163 does not exist at index 0 at /Library/Perl/5.30/PDF/API2/Basic/PDF/File.pm line 783.
    

    The error message is unclear.

    First commit in #243 creates better message like:

    PDF encryption is not implemented yet: Filter=Standard R=3 V=2 CFM=? at __PATH__/perl5/lib/perl5/PDF/Builder.pm line 684.
    

    This means that encryption is not implemented and standard is Revision=3, Version=2.

    And third commit implements decryption of R=2.

    Next steps are for discussion:
    I think the best way is implement encryption for R=2 (there are two cases for new document and for opened document). We need to think about API, Objects etc. We could define right way, which is working for the future. And after it we could implement newer revisions of encryption in order R3, R4, R5, R6. That's my proposal.

  5. PhilterPaper commented on Sep 22, 2026

    @PhilterPaper
    OwnerAuthor

    OK, let me digest this for a bit. I like the idea that more encryption methods will be added. What are your concerns regarding "API, Objects, etc."? Are you looking at some major architectural change? What do you see for overall encrypted PDF support in PDF::Builder? This needs to be documented, too.

    If I understand you correctly, this (at least) will give a cleaner error message (and maybe future support) for API2 ticket 48.

    I do have one concern at the moment. While Digest::MD5 is core with Perl (at least, on Strawberry Perl, which I develop and test with), Crypt::RC4 is not core and would need to be added. Since not many people use encrypted PDFs, and it complicates the commit workflow (I need to add these two prerequisites to the Git instructions), I am leaning towards making it an optional manual install, so encryption.t would need to be skipped if these two libraries are not available. They would not be listed in Makefile.PL, or the META.* files. Of course, I would have the libraries installed on my development and test platforms and will run encryption.t routinely. What are your thoughts on this?

  6. michal-josef-spacek commented on Sep 22, 2026

    @michal-josef-spacek

    About API
    I don't know yet, I need to test. Actually, in third commit there is new PDF::Builder::Security::Standard module which handle auth and decryption. In future encryption methods. In more future we probably need more modules, maybe for each revision. I don't know what's good approach now. We will see with code. I understand, that we need to define some API etc.

    About module loading
    If you want to remove these modules from requires, it's possible. So you need one place for loader with some nice error. Do you have one? Or is require enough? We need to change tests to skip, if the encryption/decryption module isn't present.
    Note: From my perspective as Fedora packager it's better to have all Perl modules present in required or suggested list (Perl Core modules too).

    Process from my side
    a) better error message (first commit), it's clean, tested. We could discuss message style, error framework. The place for detection of encryption in PDF file is fine, I think.
    b) adding of sample files with different encryption (first commit, second commit, I have another files), it's clean, tested.
    c) adding of decryption with R2 (third commit). I am adding some internal things, new module. We could discuss more, probably internal names, documentation is not needed probably.
    d) adding of encryption with R2 (only for new file). I have some prototype, that's for bigger discussion. We need to define constructor parameters, ideally for future versions of encryption. This means 'revision' and 'user_password', 'owner_password' keys. Maybe parameters related to concrete revision. Default values, etc.
    e) The same as d) for opened document. This is tricky, because c) decrypts the original stream, so we need to encrypt all (one option) or save the original encrypted streams and save them (second option). We need to discuss.
    f) With c) and d) and e) we could look to concept and finalize documentation and API.
    g) We could continue with R3, R4, R5 and R6 in same style. Implement decryption and encryption.

    If you need to split PR to more PRs, I could. I think that's good to understand, what we are doing.

  7. PhilterPaper commented on Sep 22, 2026

    @PhilterPaper
    OwnerAuthor

    I wonder, before we make a lot of changes, if it would be worth putting all the encryption code into a separate module (both on GitHub and on CPAN). Either you or I could own it. It might be feasible if a clean API can be made for it, so PDF::Builder could just make a few calls and not a lot of code gets buried in Builder. Builder would give a clean error message if encryption is needed and the module is not installed (the new module would handle error messages for unsupported encryption methods). It would be manually installed by the user, rather than being a prerequisite (see README.md for the long list of modules that can be installed to support various Builder functions, but are not absolutely required).

    You mentioned plans to add a number of additional encryption methods. If this is quite a bit of code, it may well be worth cleanly separating into a new module. Perhaps you could even get PDF::API2 to support such a module, if the API is relatively sweet and simple. What are your thoughts?

  8. PhilterPaper commented on Sep 23, 2026

    @PhilterPaper
    OwnerAuthor

    Taking another quick look through the code, I think you forgot to put some code in Builder.pm. It has the POD for 'password', but the code to pass it to File->open() seems to be missing, I presume it's OK if 'password' isn't given, and an undef value is passed to File->open().

    The PDF definition (for 1.7) says that to use RC4 may require a license from RSA Security! Do you know if this requirement has been removed? I certainly don't want to get into trouble over distributing RC4-using code. I also don't know if it would be sufficient to warn users that if they want to use RC4, they need to get a license. What can you tell me about the state of licensing requirements?

    I'm thinking, for a separate Perl package to import (if user attempts to use encryption), that it might have a global flag in the PDF object. A value of -1 means "I haven't checked yet if the library [let's call it PDFencrypt] is available". 0 means "A request was made for PDFencrypt but I couldn't load it. All encryption attempts will fail." 1.. means "I have loaded PDFencrypt and the requested type is available." We just want to look for and load PDFencrypt only once, saving overhead on subsequent calls. PDFencrypt would prereq any required libraries such as Crypt::RC4, leaving PDF::Builder clean. Would that satisfy your packaging concerns for Fedora? PDFencrypt might be in the flavor of CAM::PDF::Decrypt or PDF::Make::Crypt modules.

    As you mentioned, t/encryption.t will need to check if PDFencrypt is installed, and if not, SKIP. I'd like encryption to be optional, which means that PDFcrypt (and its required and/or optional prereq modules) isn't required to be installed. I have many optional modules (prereq modules) for PDF::Builder that I do test before release, but I don't want to burden the Git workflow or users who will typically use only a small fraction of them. They are not automatically installed (must be manually installed by the user); t-tests and examples are provided to test them out should the user want to install any of them.

    Is a PDF still considered "secure" if encryption/decryption is passed off to a separate library? I would think that a fake library would fail to decrypt strings and streams -- it's not just asking, "do we have an encryption system?" but I want to make sure we haven't overlooked anything. And we are encrypting new/revised PDF files if a password is given? I think you mentioned separate owner and user passwords at some point.

    Finally, be careful about PDF minimum versions to support various encryption features. PDF::Builder should support most (not all) of up to 1.4 features, but 1.5+ features are very limited. A 1.5+ encryption method is possible, but requires auto-setting the resulting PDF version to that level.

  9. michal-josef-spacek commented on Sep 23, 2026

    @michal-josef-spacek

    The external library is interesting idea. I need to think.

  10. michal-josef-spacek commented on Sep 23, 2026

    @michal-josef-spacek

    The PDF definition (for 1.7) says that to use RC4 may require a license from RSA Security! Do you know if this requirement has been removed? I certainly don't want to get into trouble over distributing RC4-using code. I also don't know if it would be sufficient to warn users that if they want to use RC4, they need to get a license. What can you tell me about the state of licensing requirements?

    That's interesting question. I checked it and using of RC4 algorithm is fine at the time. The using of the RSA licensed code isn't fine. So Crypt::RC4 is in Perl, probably fine. But, this is old code. We have another implementation like Crypt::RC4::XS, Crypt::OpenSSL::RC4, Crypt::Stream::RC4 etc.
    I will think about it.

  11. michal-josef-spacek commented on Sep 23, 2026

    @michal-josef-spacek

    I will look to CAM::PDF::Decrypt nebo PDF::Make::Crypt modules.

  12. PhilterPaper commented on Sep 24, 2026

    @PhilterPaper
    OwnerAuthor

    I see that your current code is only decrypting some specific encryption methods. Are you anticipating adding encryption as well as filling out a number of different encryption methods? Perhaps not immediately, but architecting for it to be done in the near future?

    I like the idea of a separate library ("PDFcrypt" or similar name), so that everything can be kept in one place, new prerequisites are brought in only if encryption is to be used, the code can be extended easily, and the library can be used by PDF::API2 and possibly CAM, PDF::Make, and other modules. If you want to own this library (GitHub and CPAN), I have no objections; if you want me to own it, I can grant you full access to the GitHub repository to work on the code (and possibly shared control of the CPAN library).

    I'm thinking in terms of an API, for the user to call a new routine, rather than overloading Builder's new().

    $pdf = PDF::Builder->new(options);
    $pdf ->encryption('ownerpw' => xxxxxx, 'userpw' => yyyyyy, 'version' => 4, etc.);
    

    The encryption() method would attempt to use PDFcrypt. Set a global flag -1 haven't tried to load PDFcrypt, 0 tried and failed (with an error message) or the requested version is not available or there is a problem such as wrong password. Either case, don't call encryption routines anywhere (but give an appropriate error message if an input file is encrypted). The other value could be a 1 to show encryption is available (with a separate internal $pdf->{'encryption'} that is an object pointer to the PDFcrypt module). You may have to get more granular with flags to show which version(s) are available, as more and more are added. I'm not sure I quite understand version number and resulting user capabilities versus other capability flags versus ?? -- it may take more changes to Builder to implement such permissions.

    Move as many encryption-related routines as possible (including Security::Standard) into PDFcrypt, leaving a minimum of code in PDF::Builder itself. File and Dict would check the encryption flag and call the appropriate routines to decrypt (and some day, encrypt?) strings and streams.

    t-tests and sample files would be in the PDFcrypt package; it's conceivable that some t-tests in PDF::Builder could test at least the basic function of PDFcrypt (including clean handling of an encrypted file with no PDFcrypt), but would have to SKIP the tests if it's not already installed. I don't want to force an install of PDFcrypt if users don't want to make use of it, dragging in all the new prereqs.

    What do you think of this approach? I think it would be a lot cleaner to put it in its own new module (that other PDF programs could use), with minimal changes to PDF::Builder. It should permit you to expand its capabilities (add new versions, add encryption) as time permits. Just be careful about how many libraries you require the user to (automatically) install -- perhaps consider doing as I do with Builder, and list packages to manually install (before or after PDFcrypt installation) to add specific encryption capabilities. There are a lot of PDF-related encryption packages out there that you might want to support some day.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions