gpt4 book ai didi

java - 附件损坏签名第 2 部分

转载 作者:行者123 更新时间:2023-12-03 18:08:30 25 4
gpt4 key购买 nike

我创建了将图像添加到现有 pdf 文档然后对其进行签名的代码,全部使用 PDFBox(请参阅下面的代码)。

代码很好地添加了图像和签名。但是,在某些文档中,Acrobat Reader 会提示“签名字节范围无效”。

问题似乎与this question中描述的问题相同。该问题的答案更详细地描述了该问题:问题在于我的代码在文档(流和表)中留下了交叉引用类型的混合。实际上,由于这会产生问题,某些文档甚至无法打开。

我的问题是:我如何防止这种情况?如何在不创建多个交叉引用类型的情况下将图像添加到现有的 pdf 文档?

public class TC3 implements SignatureInterface{

private char[] pin = "123456".toCharArray();
private BouncyCastleProvider provider = new BouncyCastleProvider();
private PrivateKey privKey;
private Certificate[] cert;

public TC3() throws Exception{
Security.addProvider(provider);
KeyStore keystore = KeyStore.getInstance("PKCS12", provider);
keystore.load(new FileInputStream(new File("resources/IIS_keystore.pfx")), pin.clone());
String alias = keystore.aliases().nextElement();
privKey = (PrivateKey) keystore.getKey(alias, pin);
cert = keystore.getCertificateChain(alias);
}

public void doSign() throws Exception{
byte inputBytes[] = IOUtils.toByteArray(new FileInputStream("resources/rooster.pdf"));
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(new File("resources/logo.jpg")));
PDPage page = (PDPage)pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdDocument.save(os);
os.flush();
pdDocument.close();

inputBytes = os.toByteArray();
pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));

PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("signer name");
signature.setLocation("signer location");
signature.setReason("reason for signature");
signature.setSignDate(Calendar.getInstance());

pdDocument.addSignature(signature, this);

File outputDocument = new File("resources/signed.pdf");
ByteArrayInputStream fis = new ByteArrayInputStream(inputBytes);
FileOutputStream fos = new FileOutputStream(outputDocument);
byte[] buffer = new byte[8 * 1024];
int c;
while ((c = fis.read(buffer)) != -1)
{
fos.write(buffer, 0, c);
}
fis.close();
FileInputStream is = new FileInputStream(outputDocument);

pdDocument.saveIncremental(is, fos);
pdDocument.close();
}

public byte[] sign(InputStream content) {
CMSProcessableInputStream input = new CMSProcessableInputStream(content);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
List<Certificate> certList = Arrays.asList(cert);
CertStore certStore = null;
try{
certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), provider);
gen.addSigner(privKey, (X509Certificate) certList.get(0), CMSSignedGenerator.DIGEST_SHA256);
gen.addCertificatesAndCRLs(certStore);
CMSSignedData signedData = gen.generate(input, false, provider);
return signedData.getEncoded();
}catch (Exception e){}
return null;
}

public static void main(String[] args) throws Exception {
new TC3().doSign();
}

最佳答案

问题

正如在 this answer 中已经解释的那样,这里工作的问题是

  • 当非增量存储带有添加图像的文档时,PDFBox 1.8.9 使用交叉引用表执行此操作,无论原始文件是使用表还是流;如果原始文件使用流,则交叉引用流字典条目将复制到 中。拖车字典;
    ...
    0000033667 00000 n
    0000033731 00000 n
    trailer
    <<
    /DecodeParms <<
    /Columns 4
    /Predictor 12
    >>
    /Filter /FlateDecode
    /ID [<5BD95916CAE5E84E9D964396022CBDCD> <6420B4547602C943AF37DD6C77496BE8>]
    /Info 6 0 R
    /Length 61
    /Root 1 0 R
    /Size 35
    /Type /XRef
    /W [1 2 1]
    /Index [20 22]
    >>
    startxref
    35917
    %%EOF

    (大多数 预告片 此处的条目毫无用处甚至具有误导性,请参见下文。)
  • 增量保存签名时,COSWriter.doWriteXRefInc用途 COSDocument.isXRefStream确定现有文档(我们如上存储的文档)是否使用交叉引用流。如上所述,它没有。不幸的是,COSDocument.isXRefStream在 PDFBox 1.8.9 中实现为
    public boolean isXRefStream()
    {
    if (trailer != null)
    {
    return COSName.XREF.equals(trailer.getItem(COSName.TYPE));
    }
    return false;
    }

    因此,误导拖车条目 类型 上图使 PDFBox 认为它必须使用交叉引用流。

  • 结果是一个文档,其初始修订以交叉引用表和奇怪的尾部条目结束,其第二次修订以交叉引用流结束。这是无效的。

    解决办法

    不过,幸运的是,了解问题是如何产生的可以提供一种解决方法:删除麻烦的 拖车输入,例如像这样:
        inputBytes = os.toByteArray();
    pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
    pdDocument.getDocument().getTrailer().removeItem(COSName.TYPE); // <<<<<<<<<< Remove misleading entry <<<<<<<<<<

    通过此变通方法,签名文档中的两个修订都使用交叉引用表,并且签名有效。

    请注意,如果即将推出的 PDFBox 版本更改为使用外部参照流保存从具有交叉引用流的源加载的文档,则必须再次删除该变通方法。

    不过,我认为这不会发生在 1.x.x 版本中,并且 2.0.0 版本将引入一个根本性改变的 API,所以无论如何原始代码都不会开箱即用。

    其他想法

    我也尝试了其他方法来规避这个问题,试图
  • 也将第一个操作存储为增量更新,或
  • 在与签名相同的增量更新期间添加图像,

  • 参见 SignLikeUnOriginalToo.java ,但失败了。 PDFBox 1.8.9 增量更新似乎仅适用于添加签名。

    重新审视其他想法

    在研究了更多使用 PDFBox 创建附加修订后,我再次尝试了其他想法,现在成功了!

    关键部分是将添加和更改的对象标记为更新,包括来自文档目录的路径。

    应用第一个想法(将图像添加为明确的中间修订版)相当于 doSign 中的此更改:
    ...
    FileOutputStream fos = new FileOutputStream(intermediateDocument);
    FileInputStream fis = new FileInputStream(intermediateDocument);

    byte inputBytes[] = IOUtils.toByteArray(inputStream);

    PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
    PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
    PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
    PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
    contentStream.drawXObject(ximage, 50, 50, 356, 40);
    contentStream.close();

    pdDocument.getDocumentCatalog().getCOSObject().setNeedToBeUpdate(true);
    pdDocument.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdate(true);
    page.getCOSObject().setNeedToBeUpdate(true);
    page.getResources().getCOSObject().setNeedToBeUpdate(true);
    page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
    ximage.getCOSObject().setNeedToBeUpdate(true);

    fos.write(inputBytes);
    pdDocument.saveIncremental(fis, fos);
    pdDocument.close();

    pdDocument = PDDocument.load(intermediateDocument);

    PDSignature signature = new PDSignature();
    ...

    (如 SignLikeUnOriginalToo.java 方法 doSignTwoRevisions )

    应用第二个想法(添加图像作为签名修订的一部分)相当于 doSign 中的此更改:
    ...
    byte inputBytes[] = IOUtils.toByteArray(inputStream);
    PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));

    PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
    PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
    PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
    contentStream.drawXObject(ximage, 50, 50, 356, 40);
    contentStream.close();

    page.getResources().getCOSObject().setNeedToBeUpdate(true);
    page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
    ximage.getCOSObject().setNeedToBeUpdate(true);

    PDSignature signature = new PDSignature();
    ...

    (如 SignLikeUnOriginalToo.java 方法 doSignOneStep )

    两种变体显然都比原始方法更可取。

    关于java - 附件损坏签名第 2 部分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30549830/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com