Skip to content

Instantly share code, notes, and snippets.

@pzi
Last active October 11, 2022 13:09
Show Gist options
  • Save pzi/7579e0967deb5f2e0a5bc9b7ca4819da to your computer and use it in GitHub Desktop.
Save pzi/7579e0967deb5f2e0a5bc9b7ca4819da to your computer and use it in GitHub Desktop.
Async method to determine whether or not a PDF has any encryption applied to it.
export const checkPDFEncryption = (file: Blob): Promise<boolean> => {
const allTrailerOccurrences = (source: string, search: string) => {
const trailers = [];
// TODO: Reverse the search as trailers should appear at the end of the file according to the spec.
for (let i = 0; i < source.length; ++i) {
if (source.substring(i, i + search.length) === search) {
trailers.push(i);
}
}
return trailers;
};
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = (result) => {
const fileData = result.target?.result;
let isEncrypted = false;
if (fileData instanceof ArrayBuffer) {
let fileAsString = '';
// First we need to convert the entire PDF file from ArrayBuffer to human-readable text.
if ('TextDecoder' in window) {
// Decode as UTF-8
const dataView = new DataView(fileData);
const decoder = new TextDecoder('utf-8');
fileAsString = decoder.decode(dataView);
} else {
// Fallback method
const dataArray = new Uint8Array(fileData);
for (const byte of dataArray) {
// Convert decimal to UTF-8 character (e.g. 87 -> W)
fileAsString += String.fromCharCode(byte);
}
}
// # According to the official PDF spec (Page 55):
//
// Encryption-related information is stored in a document's _encryption dictionary_, which
// is the value of the *Encrypt* entry in the document's *trailer* dictionary.
// The absence of this entry from the trailer dictionary means that a conforming reader
// should consider the given document to be not encrypted.
// See page 55: https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
//
// Example of a trailer dictionary:
// trailer
// << /Size 8
// << /Root 1 0 R
// << /Encrypt 8 0 R
// This will find all trailers in a given document (in case there are multiple).
// Reference: https://resources.infosecinstitute.com/topic/pdf-file-format-basic-structure/
const allTrailers = allTrailerOccurrences(fileAsString, 'trailer');
// Then we need to go through each trailer and look for the "Encrypt" keyword
allTrailers.forEach((trailer) => {
const encryptKey = fileAsString.indexOf('Encrypt', trailer);
// By looking for indexes higher than 0, we only find values like '/Encrypt' (after the `/` prefix).
if (encryptKey > 0) {
isEncrypted = true;
}
});
resolve(isEncrypted);
} else {
reject(`FileReader result was not an ArrayBuffer: ${fileReader.result}`);
}
};
fileReader.onerror = () => reject;
fileReader.readAsArrayBuffer(file);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment