使用iTextSharp打开受密码保护的PDF文件

14

我正在制作一个应用程序,应该显示带有密码的PDF。这是我的代码:

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        try
        {
            string filePath = Request.QueryString["filePath"];
            if (filePath.ToUpper().EndsWith("PDF"))
            {
                copyPDF(filePath);
            }
        }
        catch
        {
            string message = "<script language='Javascript'>alert('File Not Found! Call Records Department for verification. ')</script>";
            ScriptManager.RegisterStartupScript(Page, this.GetType(), message, message, false);
        }
    }
}
public void copyPDF(string filePath)
{
    iTextSharp.text.pdf.RandomAccessFileOrArray ra = new iTextSharp.text.pdf.RandomAccessFileOrArray(Server.MapPath(ResolveUrl(filePath)));
    if (ra != null)
    {
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        byte[] password = System.Text.ASCIIEncoding.ASCII.GetBytes("Secretinfo");
        iTextSharp.text.pdf.PdfReader thepdfReader = new iTextSharp.text.pdf.PdfReader(ra, password);
        int pages = thepdfReader.NumberOfPages;
        iTextSharp.text.Document pdfDoc = new iTextSharp.text.Document();
        iTextSharp.text.pdf.PdfCopy pdfCopy = new iTextSharp.text.pdf.PdfCopy(pdfDoc, ms);

        pdfDoc.Open();
        int i = 0;
        while (i < pages)
        {
            pdfCopy.AddPage(pdfCopy.GetImportedPage(thepdfReader, i + 1));
            i += 1;
        }
        pdfDoc.Close();
        Byte[] byteInfo = ms.ToArray();
        Response.Clear();
        Response.ContentType = "application/pdf";
        Response.AddHeader("content-length", byteInfo.Length.ToString());
        Response.BinaryWrite(byteInfo);
        Response.Flush();
        Response.End();
    }
}

我的代码可以无需密码打开pdf文件,但是即使提供了密码,它也无法打开带有密码的pdf文件。应用程序执行了异常处理。我的代码有什么问题?

编辑:我删除了Catch以查看抛出的异常。

异常详情:System.ArgumentException:PdfReader未使用所有者密码打开

它说错误的源是第51行。

Line 49:    while (i < pages)
Line 50:    {
Line 51:         pdfCopy.AddPage(pdfCopy.GetImportedPage(thepdfReader, i + 1));
Line 52:         i += 1;
Line 53:    }

抛出了哪个异常? - mkl
它不会抛出异常。它执行的是catch语句,这种情况下是一个弹出窗口,上面写着“文件未找到!请联系记录部门进行验证。” - Artemis
嘿 @mkl,我移除了Catch语句以查找抛出的异常类型。它显示System.ArgumentException: PdfReader未使用所有者密码打开 - Artemis
似乎您没有提供正确的密码。 - Darin Dimitrov
我做了,我检查了,大概10次。也许我需要再次提供密码,在第51行的某个地方? - Artemis
1
也许PDF文件同时设置了用户密码和所有者密码,而你只提供了用户密码。 - mkl
4个回答

23
对于iText(Sharp)加密文档的某些操作,需要使用所有者密码而不仅仅是用户密码打开文档。这符合PDF规范中这些密码的定义:
解密文档是否允许执行其他操作取决于打开文档时提供的密码(如果有)以及创建文档时指定的任何访问限制:
- 使用正确的所有者密码打开文档应允许完全(所有者)访问文档。此无限制访问包括更改文档的密码和访问权限的能力。 - 使用正确的用户密码(或使用默认密码打开文档)打开文档应允许根据文档的加密字典中指定的用户访问权限执行其他操作。
(见ISO 32000-1第7.6.3.1节) iText(Sharp)目前并不详细检查文档加密字典中指定的用户访问权限,而是始终要求拥有者密码来执行需要特定权限的操作,从文档中复制整个页面就是其中之一。
这意味着,由于经常被问及此类问题,iText(Sharp)开发人员非常清楚:
  • iText(Sharp)用户可能有权在没有拥有者密码的情况下执行此类操作,原因是文档的加密字典中指定了相应的用户访问权限;
  • 有许多PDF文档的所有者使用拥有者密码(以防止他人滥用),但忘记了密码(或者根本不知道随机生成的密码),
  • iText(Sharp)是开源的,任何人都可以轻松地对其进行修补,使其不再尊重用户和拥有者密码之间的差异。
为了允许用户执行其应有的操作,并防止库的补丁版本传播,iText(Sharp)PdfReader类中包含了一个覆盖此测试的方法。
/**
 * The iText developers are not responsible if you decide to change the
 * value of this static parameter.
 * @since 5.0.2
 */
public static bool unethicalreading = false;

因此,通过设置
PdfReader.unethicalreading = true;

你可以全局覆盖此权限检查机制。
请尊重PDF作者的权利,仅在确实有权执行相关操作时使用此覆盖功能。

我使用了这个,现在出现了一个 InvalidCastException 错误 无法将类型为 'iTextSharp.text.pdf.PdfArray' 的对象强制转换为类型 'iTextSharp.text.pdf.PRIndirectReference'. - Artemis
那么,要么是您的PDF文件有误,要么是itext出了问题。您能提供PDF文件以进行分析吗? - mkl
我怎么能向您提供PDF呢?(很抱歉,如果您告诉我该怎么做,我将非常乐意。) - Artemis
Stackoverflow本身没有文件交换服务。因此,在Stackoverflow上提供文件时,通常会使用一些文件共享服务,例如通过Dropbox或Google Drive提供文件,并在此处发布URL。 - mkl
该文档似乎不是公开可用的。谷歌云盘告诉我“您需要获得许可”。 - mkl
显示剩余2条评论

3

尝试使用这个解决方法来解锁受保护的PdfReader。对我来说很有效:

public static PdfReader TryToUnlockPdf(PdfReader reader)
{
    if (reader == null)
    {
        return reader;
    }
    try
    {
        var f = reader.GetType().GetField("encrypted", BindingFlags.NonPublic | BindingFlags.Instance);
        f?.SetValue(reader, false);
    }
    catch (Exception)
    { // ignore
    }
    return reader;
}

private static void GetPdfPageFiles(this Page pageFile)
{
    reader = new PdfReader(pageFile.ContentBytes);
    // Unlock protected 
    reader = TryToUnlockPdf(reader);

    // if no using TryToUnlockPdf workaroud - GetImportedPage method raises "System.ArgumentException: PdfReader not opened with owner password"
    var curPage = pdfWriter.GetImportedPage(reader, 0);
}

1
我应用了这个解决方法,它有效:
private void fixIssue(PdfReader pdfReader) throws Exception {
        Field f = pdfReader.getClass().getDeclaredField("ownerPasswordUsed");
        f.setAccessible(true);
        f.setBoolean(pdfReader, true);

}

虽然我知道在iText中有一些情况需要使用反射来实现有趣的效果,但在这里并不需要。 - mkl
这只是一个解决方法,就像我写的那样。但它有效 :) 可能更有意义的做法是使用提供的构造函数之一来将ownerPasswordUsed设置为true。http://sourceforge.net/p/itext/code/HEAD/tree/trunk/itext/src/main/java/com/itextpdf/text/pdf/PdfReader.java - Enrico Giurin
@mkl 有这个需求。我使用旧版的iTextSharp 4.x来避免商业许可证,但没有简单的方法来设置该字段。 - Chris Bordeman
@ChrisBordeman 好的,所以在问题的上下文中没有必要这样做,因为OP显然使用了更新版本的iText。对于5.0.2之前的iText版本,反射确实是唯一的解决方案,如果不想修补库本身的话。 - mkl

0
PdfReader.AllowOpenWithFullPermissions = true;

pdfReader = new PdfReader(filePath);

你可能需要提到 PdfReader.AllowOpenWithFullPermissions 标志是特定于 iTextSharp.LGPLv2 分支的。原始的 iTextSharp 使用 PdfReader.unethicalreading 标志。 - mkl

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