Last active
October 11, 2022 13:09
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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