// encryptMetadata is true if not present. Note that we don't actually
// use this to change our reading of metadata streams (that's all done
// internally by the document specifying a Crypt filter of None if
// appropriate), but it does affect the encryption key.
boolean encryptMetadata = true;
final PDFObject encryptMetadataObj =
encryptDict.getDictRef("EncryptMetadata");
if (encryptMetadataObj != null
&& encryptMetadataObj.getType() == PDFObject.BOOLEAN) {
encryptMetadata = encryptMetadataObj.getBooleanValue();
}
// Assemble decrypters for each filter in the
// crypt filter (CF) dictionary
final Map<String, PDFDecrypter> cfDecrypters =
new HashMap<String, PDFDecrypter>();
final PDFObject cfDict = encryptDict.getDictRef("CF");
if (cfDict == null) {
throw new PDFParseException(
"No CF value present in Encrypt dict for V4 encryption");
}
final Iterator<String> cfNameIt = cfDict.getDictKeys();
while (cfNameIt.hasNext()) {
final String cfName = cfNameIt.next();
final PDFObject cryptFilter = cfDict.getDictRef(cfName);
final PDFObject lengthObj = cryptFilter.getDictRef("Length");
// The Errata for PDF 1.7 explains that the value of
// Length in CF dictionaries is in bytes
final Integer length = lengthObj != null ?
lengthObj.getIntValue() * 8 : null;
// CFM is the crypt filter method, describing whether RC4,
// AES, or None (i.e., identity) is the encryption mechanism
// used for the name crypt filter
final PDFObject cfmObj = cryptFilter.getDictRef("CFM");
final String cfm = cfmObj != null ?
cfmObj.getStringValue() : "None";
final PDFDecrypter cfDecrypter;
if ("None".equals(cfm)) {
cfDecrypter = IdentityDecrypter.getInstance();
} else if ("V2".equals(cfm)) {
cfDecrypter = createStandardDecrypter(
encryptDict, documentId, password, length,
encryptMetadata,
StandardDecrypter.EncryptionAlgorithm.RC4);
} else if ("AESV2".equals(cfm)) {
cfDecrypter = createStandardDecrypter(
encryptDict, documentId, password, length,
encryptMetadata,
StandardDecrypter.EncryptionAlgorithm.AESV2);
} else {
throw new UnsupportedOperationException(
"Unknown CryptFilter method: " + cfm);
}
cfDecrypters.put(cfName, cfDecrypter);
}
// always put Identity in last so that it will override any
// Identity filter sneakily declared in the CF entry
cfDecrypters.put(CF_IDENTITY, IdentityDecrypter.getInstance());
PDFObject stmFObj = encryptDict.getDictRef("StmF");
final String defaultStreamFilter =
stmFObj != null ? stmFObj.getStringValue() : CF_IDENTITY;
PDFObject strFObj = encryptDict.getDictRef("StrF");
final String defaultStringFilter =
strFObj != null ? strFObj.getStringValue() : CF_IDENTITY;
return new CryptFilterDecrypter(
cfDecrypters, defaultStreamFilter, defaultStringFilter);
}