如何检查PDF文件是否受密码保护?

3
如何在Java中检查PDF文件是否受密码保护? 我知道有几个工具/库可以做到这一点,但我想知道是否可能只用Java程序来实现。

1
你已经回答了自己的问题。使用开源库,如iText,并编写一个Java程序进行测试。 - sudmong
我猜这些工具/库中有一些是用纯java编写的(而其他一些可能是用其他语言编写的,但这不是主题),对吧? :) 如果是这样,为什么不能编写自己的密码检查实现呢? - aga
正如@aga所指出的那样,Java显然可以做到这一点。因此,您的问题很可能缺少您没有提到的其他要求。请解释一下您的需求。 - mkl
6个回答

3
你可以使用PDFBox: http://pdfbox.apache.org/ 代码示例:
try
{
    document = PDDocument.load( yourPDFfile );

    if( document.isEncrypted() )
    {
      //ITS ENCRYPTED!
    }
}

正在使用Maven吗?

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0</version>
</dependency>

另外,使用 getNumberOfPages() 获取页面数量也是非常有帮助的。 - 0101100101

3

更新

根据mkl在下面的评论中提到,似乎规范允许两种PDF结构: (1) 交叉引用表格 (2) 交叉引用流。以下解决方案仅针对第一种结构。需要更新此答案以解决第二种结构。

====

以上所有答案都涉及某些第三方库,而这正是OP已经知道的。OP正在寻求本机Java方法。我的答案是可以,但需要大量工作。

这将需要两个步骤:

步骤1: 确定PDF是否加密

根据Adobe的PDF 1.7规范(第97页和115页),如果尾部记录包含键“\Encrypted”,则PDF已加密(加密可能是简单的密码保护、RC4或AES或某些自定义加密)。以下是一个示例代码:

    Boolean isEncrypted = Boolean.FALSE;
    try {
        byte[] byteArray = Files.readAllBytes(Paths.get("Resources/1.pdf"));
        //Convert the binary bytes to String. Caution, it can result in loss of data. But for our purposes, we are simply interested in the String portion of the binary pdf data. So we should be fine.
        String pdfContent = new String(byteArray);
        int lastTrailerIndex = pdfContent.lastIndexOf("trailer");
        if(lastTrailerIndex >= 0 && lastTrailerIndex < pdfContent.length()) {
            String newString =  pdfContent.substring(lastTrailerIndex, pdfContent.length());
            int firstEOFIndex = newString.indexOf("%%EOF");
            String trailer = newString.substring(0, firstEOFIndex);
            if(trailer.contains("/Encrypt"))
                isEncrypted = Boolean.TRUE;
        }
    }
    catch(Exception e) {
        System.out.println(e);
        //Do nothing
    }
步骤2: 确定加密类型 这一步比较复杂,暂时还没有代码示例。但以下是算法:
  1. 从步骤1中读取尾部的键"/Encrypt"的值,例如值为"288 0 R"。
  2. 查找字节"288 0 obj"。这是文档中“加密字典”对象的位置。该对象的边界以字符串“endobj”结束。
  3. 在此对象中查找键"/Filter"。"Filter"用于标识文档的安全处理程序。如果"/Filter"的值为"/Standard",则文档使用内置的基于密码的安全处理程序。
如果您只想知道PDF是否已加密而不用担心加密形式是所有者/用户密码还是某些高级算法,则不需要执行上述步骤2。
希望能对您有所帮助。

@mkl 你能否举个例子详细说明一下?如果需要的话,我很乐意更新我的回答。我们的目标是为社区提供最有效的答案。 - VHS
一个使用交叉引用流的pdf文件不符合您的代码所需的尾部结构。相反,尾部条目被添加到交叉引用流字典中。 - mkl
@mkl,您能否附上一个使用xref流而不是xref表的示例PDF文件?规范清楚地说明加密信息存储在尾部字典中。 - VHS
由于我目前只使用智能手机,手头没有我的样本文件。但是请查看规范ISO 32000-1,第7.5.8节“交叉引用流”:交叉引用流是流对象(请参见7.3.8,“流对象”),包含一个字典和一个数据流。每个交叉引用流包含与交叉引用表(请参见7.5.4,“交叉引用表”)和尾部(请参见7.5.5,“文件尾”)等价的信息,用于一个交叉引用部分。 - mkl
@mkl,感谢您的解释和示例。在我找到交叉引用流结构的解决方案之前,我已将此信息放入答案中。 - VHS
显示剩余2条评论

0
正确的Java编程操作方式是根据 @vhs 的建议。
然而,在任何应用程序中,最简单的方法是使用非常轻量级的pdfinfo工具来过滤加密状态,在这里使用Windows命令提示符即可立即获得报告,指出同一文件的两个不同副本都已经加密。
>forfiles /m *.pdf /C "cmd /c echo @file &pdfinfo @file|find /i \"Encrypted\""

"Certificate (9).pdf"
Encrypted:      no

"ds872 source form.pdf"
Encrypted:      AES 128-bit

"ds872 filled form.pdf"
Encrypted:      AES 128-bit

"How to extract data from a particular area in a PDF file - Stack Overflow.pdf"
Encrypted:      no

"Test.pdf"
Encrypted:      no

>

0

使用 iText pdf API,我们可以识别受密码保护的 PDF。

示例:

    try {
            new PdfReader("C:\\Password_protected.pdf");            
        } catch (BadPasswordException e) {
            System.out.println("PDF is password protected..");
        } catch (Exception e) {
            e.printStackTrace();
        }

iText采用AGPL许可证。因此,我建议使用PDFBox。 - rbrisuda
读取器不足以打开pdf。第二行应该是 new PdfDocument(new PdfReader(filePath)); - Thomas Pierre

0

您可以使用Itext验证pdf,即可读写。

以下是代码片段:

boolean isValidPdf = false;
try {
    InputStream tempStream = new FileInputStream(new File("path/to/pdffile.pdf"));
    PdfReader reader = new PdfReader(tempStream);
    isValidPdf = reader.isOpenedWithFullPermissions();
    } catch (Exception e) {
        isValidPdf = false;
    }

-1
解决方案:
1)安装PDF解析器http://www.pdfparser.org/ 2)在Parser.php文件中编辑此部分:
if (isset($xref['trailer']['encrypt'])) {
echo('Your Allert message');
exit();}

3) 在您的 .php 表单提交页面(例如 upload.php)中插入以下内容:

for the first require  '...yourdir.../vendor/autoload.php';

然后编写这个函数:

function pdftest_is_encrypted($form) {
$parser = new \Smalot\PdfParser\Parser();
$pdf    = $parser->parseFile($form);
}

然后调用函数

pdftest_is_encrypted($_FILES["upfile"]["tmp_name"]);

这就是全部内容了,如果您尝试加载一个有密码的PDF文件,系统会返回一个错误信息“您的警告消息”。


2
你的回答是针对PHP的,但问题要求Java解决方案。 - saschoar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接