How to Password-Protect Generated PDFs in Ruby
Protect generated PDFs in Ruby with an opening password. Compare HexaPDF and Prawn, configure encryption, and understand the limits of PDF permissions.

To password-protect a generated PDF in Ruby, use the PDF library’s encryption support and set a user (opening) password. With Prawn, call encrypt_document inside the document block. With HexaPDF, use HexaPDF::Document#encrypt and check the option names for your installed version. For new work that needs current AES options or manipulation of existing PDFs, HexaPDF is the stronger fit in the documentation reviewed. Prawn’s 2.5.0 security documentation describes a 40-bit password-derived key, so do not rely on it for highly sensitive files without a separate security review.
An owner password and PDF permission flags are not substitutes for an opening password. Permissions may be enforced inconsistently by PDF readers. Use an actual user password, deliver it separately from the PDF, and verify the result in the readers your application supports.
1. Choose the library for the job
| Need | Choose | Reason and caveat |
|---|---|---|
| Modern documented AES options or edits to existing PDFs | HexaPDF | It creates and manipulates PDFs and documents AES 128-bit and 256-bit options. Its guide recommends AES 128-bit for broad compatibility. |
| Existing generation code already uses Prawn | Prawn, with caution | Its manual includes encrypt_document. The Prawn 2.5.0 API documents a 40-bit password-derived key and warns that readers may not enforce permissions. |
HexaPDF requires Ruby 3.0 or newer according to its project repository. It is distributed under AGPL and a commercial license; its project documents licensing requirements for some proprietary distribution and network-access deployments. Check the current terms against your deployment before adopting it. Prawn’s encryption behavior and available options are version-specific too, so consult the documentation matching your installed release.
2. Understand the PDF password model
- User password: the opening password a recipient enters to view the file. Set this if the requirement is that ordinary opening requires a password.
- Owner password: provides owner-level access and can permit changing or overriding restrictions. It is separate from the user password.
- Permissions: requests to restrict printing, copying, modification, or annotations. These flags are not a dependable confidentiality boundary because readers can enforce them differently.
Both libraries document encrypted PDFs that can be opened without a password when the user password is empty or omitted. That is not password-gated viewing. If opening must be blocked, configure a non-empty user password and test that a wrong password is rejected.

3. Generate and encrypt with Prawn
Install Prawn in your application, for example by adding gem 'prawn' to the Gemfile and running bundle install. The following is a complete script for a basic generated document. Replace the placeholder password with a secret supplied at runtime; do not commit a real password.

require 'prawn'
user_password = ENV.fetch('PDF_USER_PASSWORD')
owner_password = ENV.fetch('PDF_OWNER_PASSWORD')
Prawn::Document.generate('report.pdf') do |pdf|
pdf.encrypt_document(
user_password: user_password,
owner_password: owner_password
)
pdf.text 'Monthly report'
pdf.move_down 12
pdf.text 'This PDF requires the user password to open.'
end
Run it with environment variables set in your shell or secret manager:
PDF_USER_PASSWORD='recipient-secret' PDF_OWNER_PASSWORD='owner-secret' ruby generate_report.rb
Prawn’s security API says permission options default to true and names controls for printing, content modification, copying, and annotation modification. You can pass permission options to encrypt_document as documented for your Prawn version, but treat them as reader-facing restrictions rather than protection from copying or extraction. The 2.5.0 API explicitly describes its encryption as limited to a 40-bit password-derived key and says PDF readers are not required to honor permissions. Its warning is specifically about that implementation and those permission controls, not about every PDF encryption scheme.
If your application already has a Prawn document block, place the encryption call inside that block as shown. Ensure the user password is non-empty; Prawn documents that an omitted or empty user password can leave the encrypted document readable without a password.
4. Encrypt with HexaPDF
Install HexaPDF through your application’s dependency management, then use the installed version’s API reference for exact option names and accepted values. The encryption entry point is HexaPDF::Document#encrypt. This version-conscious example illustrates the structure: create the document, add content, configure encryption, then write it.
require 'hexapdf'
user_password = ENV.fetch('PDF_USER_PASSWORD')
owner_password = ENV.fetch('PDF_OWNER_PASSWORD')
raise 'Set a non-empty PDF_USER_PASSWORD' if user_password.empty?
pdf = HexaPDF::Document.new
page = pdf.pages.add
canvas = page.canvas
canvas.font('Helvetica', size: 18)
canvas.text('Monthly report', at: [50, 750])
# Check the installed HexaPDF version's encryption guide for the
# exact option names and values accepted by Document#encrypt.
pdf.encrypt(
user_password: user_password,
owner_password: owner_password
)
pdf.write('report.pdf')
HexaPDF’s official encryption guide describes AES 128-bit as its default and broad-compatibility choice. It also documents AES 256-bit, standardized with PDF 2.0 (earlier support was an Adobe extension), and says old RC4 should be avoided. Confirm how your installed version selects an algorithm; do not assume an option name from another release. Use its API reference for the exact parameters and values.
HexaPDF can also open encrypted files. Its API documents supplying the password in decryption_opts to HexaPDF::Document.new. Consult that version’s API for the exact construction syntax when modifying an existing protected PDF; never assume the output will preserve the original encryption policy unless you explicitly configure and verify it.
5. Verify the result before delivery
- Generate a sample with a non-empty user password and a distinct owner password.
- Open it in each supported desktop or browser PDF reader. Confirm it prompts for the user password.
- Try an incorrect password and confirm the document does not open.
- Open it with the correct password and check that expected pages, fonts, and content render.
- If you configure permissions, inspect behavior in supported readers, while remembering that permission enforcement is not robust security.
- Repeat this check after library upgrades or changes to encryption options.
This is a recommended validation workflow; it is not a report of tests performed for this article. PDF readers can differ in how they handle permissions and encryption revisions, so test the actual client environments that matter to your application.
6. Keep secrets and files dependable
- Keep passwords out of source: retrieve them from a secret manager or environment-specific configuration. Avoid logging passwords or including them in exception messages.
- Send the password separately: do not attach the opening password in the same email or message as the PDF if the goal is to reduce exposure from a forwarded file.
- Plan password recovery: if the recipient loses the password, the application may be unable to recover access. Define a secure reissue process.
- Write atomically where practical: generate to a temporary path, confirm the write succeeded, then move it into place. This avoids exposing a partially written output if the process is interrupted.
- Control temporary copies: encrypted output can still leave sensitive source data in logs, temporary HTML, caches, or unprotected intermediate PDFs.
- Mind compatibility: stronger or newer encryption revisions may not work in older reader software. Choose based on supported clients and verify there.
7. Troubleshooting common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| PDF opens without asking for a password | User password was empty, omitted, or not passed to the encryption call. | Set a non-empty user password, confirm the call is made before writing, and regenerate. |
| “Unknown keyword” or option error | Example options do not match the installed gem version. | Read the API reference for that exact version; verify the bundle’s resolved version before changing code. |
| Wrong password is accepted or restrictions appear absent | You may be testing owner access, or expecting a reader to enforce permissions. | Test the opening password separately. Treat permission flags as advisory reader behavior, not confidentiality controls. |
| Recipient cannot open the file | Password mismatch, incompatible reader, damaged output, or encryption setting unsupported by an older reader. | Recreate from source, verify the password through a secure channel, and test with the recipient’s reader and selected algorithm. |
| HexaPDF fails on the deployed runtime | The runtime may be older than the project’s stated Ruby 3.0 minimum. | Use a supported Ruby runtime or select a library compatible with the application’s constraints. |
| Deployment or distribution licensing is unclear | HexaPDF’s AGPL and commercial licensing terms may affect the deployment model. | Review the current vendor terms for the actual proprietary, hosted, or distributed application before release. |
| The output file is missing or incomplete | Generation raised an error or the destination could not be written. | Check the exception, directory permissions, available disk space, and whether the process writes to the intended path. |
8. Performance, reliability, and cost
Encryption is one stage in document generation; total runtime and memory are usually also shaped by page count, embedded images, fonts, and whether the application is rebuilding or manipulating a large existing PDF. The reviewed documentation does not establish benchmark figures, so measure with representative documents rather than relying on a generic throughput estimate.
For reliability, pin the library version, preserve the exact encryption configuration alongside application configuration, and include a small opening-password check in your release workflow. Keep source PDFs and generated outputs distinct so a failed encryption step cannot silently deliver an unprotected original. Avoid retries that overwrite a known-good artifact until the replacement has been written successfully.
There is no per-document fee described in the reviewed library documentation. Budget instead for engineering, runtime, storage, secure delivery, support, and—where relevant—HexaPDF commercial licensing. Encryption does not erase the need to protect the source material and keys.
Or skip the browser setup
If the PDF you need is a website capture rather than a document your Ruby app composes, ScreenshotNeo can return a PDF from one GET request. Its API is for website screenshots and PDFs; it does not replace Prawn or HexaPDF for encrypting an arbitrary generated PDF. See the ScreenshotNeo API documentation for options and response details.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-d format=pdf \
-o page.pdf
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots through MCP tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Those facts apply to ScreenshotNeo website captures, not PDF encryption.
Sign up free for 1,000 screenshots a month, no card required.
FAQ
How do I set a PDF open password?
Set a non-empty user password in the library’s PDF encryption configuration. In Prawn, that is the user_password argument to encrypt_document; in HexaPDF, configure encryption through Document#encrypt using the installed version’s documented options.
Can people still print or copy a password-protected PDF?
Permission flags may request restrictions, but readers are not required to enforce them uniformly. Do not use those flags as a dependable barrier to copying or printing.
Should I use the same user and owner password?
Use separate values so the recipient’s opening credential is distinct from owner-level access. Store and deliver both appropriately, and avoid hard-coding either one.
Can I encrypt an existing PDF after generating it?
HexaPDF is a PDF manipulation library as well as a generator, and documents encryption and decryption. Follow the API for your installed version and verify the resulting file. Prawn’s cited workflow is document generation through its document block.
Is PDF password protection enough for highly sensitive data?
That depends on the threat model, library, encryption settings, reader compatibility, and key handling. In particular, Prawn 2.5.0 documents a 40-bit key limitation; obtain a security review and choose a suitable approach before using it for sensitive material.


