Change PDF Passwords
Use recrypt to encrypt, re-encrypt, or remove encryption from a PDF. Supply
password when opening an already encrypted input; set output passwords with
userPassword and ownerPassword.
muhammara.recrypt("input.pdf", "output.pdf", {
password: "current-password",
userPassword: "new-open-password",
ownerPassword: "new-owner-password",
userProtectionFlag: 4,
});
To remove encryption, provide the input password without new output password
options.
Without Blocking the Event Loop
recrypt does all of its work on the JavaScript thread, so a server answers no
other request while a document is re-encrypted. recryptAsync takes the same
arguments and options, re-encrypts on libuv's thread pool, and returns a
promise.
await muhammara.recryptAsync("input.pdf", "output.pdf", {
password: "current-password",
userPassword: "new-open-password",
});
Stream objects work as with recrypt:
var target = new muhammara.PDFWStreamForFile("output.pdf");
await muhammara.recryptAsync(
new muhammara.PDFRStreamForBuffer(sourceBuffer),
target,
{ userPassword: "new-open-password" },
);
await new Promise((resolve) => target.close(resolve));
Wrong arguments, such as a missing destination or a path mixed with a stream,
throw synchronously, as with recrypt. Every other failure rejects the
promise. A wrong input password or an unreadable source rejects with the
message recrypt throws. An error thrown by the source or output stream, even
while recryptAsync reads the source, rejects with that error. A stream job
whose source or output is too large to hold in memory rejects with a
RangeError. A path that is too long for the system once made absolute
rejects with an Error; see the limitations below. An allocation that fails
inside the recrypt itself still ends the process, as with recrypt, and so
does any failed allocation in Electron.
Recipe.encrypt() encrypts with the synchronous recrypt when endPDF()
runs. To keep that step off the event loop, leave out encrypt() and recrypt
the finished file instead:
var recipe = new muhammara.Recipe("new", "plain.pdf");
recipe.createPage(595, 842).text("hello", 50, 50).endPage();
recipe.endPDF();
await muhammara.recryptAsync("plain.pdf", "output.pdf", {
userPassword: "user",
ownerPassword: "owner",
userProtectionFlag: 4,
});
One job per thread at a time
- A thread's jobs run one after another, in the order they were
started. Jobs started from different worker threads, and synchronous
recryptcalls, run in parallel and do not wait for each other. - Waiting jobs stay off the thread pool. Each thread passes one job at
a time to libuv's pool, so file system, DNS and zlib work keeps running.
Each worker thread with a running job holds one pool thread. Raise
UV_THREADPOOL_SIZEwhen many worker threads recrypt at once. - Stream jobs read and write on the calling thread. The source stream
is read into memory when
recryptAsyncis called, and the output is written to the target stream when the work is done. Both block the event loop while they run, and every waiting stream job keeps its source in memory. Use paths for large documents or many jobs. - Do not write to the target stream after the call. The output's offsets are computed from the stream position at the call. If the position changed when the job finishes, the promise rejects and nothing is written. This includes an earlier job that wrote to the same stream while this one waited.
- Relative paths are resolved when
recryptAsyncis called, includinglog, so a later change of the working directory does not affect a waiting job. They name the filesrecryptwould open at that moment, also when the path passes through a symbolic link. A path that is longer than the system allows once made absolute rejects, even whererecryptcan still open it relative to the working directory. - Use a separate output for each job, and do not change a source file while a job that reads it is waiting.
- Pass
logto each call. Each thread has its own log settings, so a writer'slogon the JavaScript thread does not apply to a job, and a job'slogdoes not apply to anything else.
To measure the difference in a server, see Benchmark Sync And Async Recrypt.
Encrypt A New PDF
Pass userPassword, ownerPassword, and optionally userProtectionFlag to
createWriter when creating an encrypted PDF. A user password opens the PDF;
the owner password controls permission changes. userProtectionFlag is the PDF
permission bit field passed to the encryption dictionary.
var writer = muhammara.createWriter("encrypted.pdf", {
version: muhammara.ePDFVersion17,
userPassword: "open-password",
ownerPassword: "owner-password",
userProtectionFlag: 4,
});
Use the same version rules below to choose the encryption algorithm. To open
this document with a low-level reader, pass the user or owner password as the
reader's password option.
The encryption algorithm is selected automatically from the PDF
version; there is no separate algorithm option.
| PDF version | Encryption algorithm | Key size |
|---|---|---|
| 1.0 through 1.3 | RC4 | 40-bit |
| 1.4 through 1.5 | RC4 | 128-bit |
| 1.6 through 1.7 | AESV2 (AES-128) | 128-bit |
| 2.0 | AESV3 (AES-256) | 256-bit |
PDF 2.0 encryption requires an OpenSSL-enabled build.