我创建了一个代码,用于将图像添加到现有的pdf文档中,然后使用PDFBox对其进行签名(请参阅下面的代码).
代码很好地添加了图像和签名.但是,在某些文档中,Acrobat Reader会抱怨"签名字节范围无效".
这个问题似乎是一样的中提到的问题这个问题.该问题的答案更详细地描述了问题:问题是我的代码在文档(流和表)中留下了交叉引用类型的混合.实际上,由于这些问题造成的问题,有些文件甚至无法打开.
我的问题是:我该如何防止这种情况发生?如何在不创建多个交叉引用类型的情况下将图像添加到现有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();
}
Run Code Online (Sandbox Code Playgroud)
当使用添加的图像非增量地存储文档时,无论原始文件是使用表还是流,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
Run Code Online (Sandbox Code Playgroud)
(这里的大多数预告片条目都是无用的,甚至是误导性的,见下文.)
当逐步保存签名时,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;
}
Run Code Online (Sandbox Code Playgroud)
因此,上面显示的误导性预告片条目类型使PDFBox认为它必须使用交叉引用流.
结果是一个文档,其初始修订以交叉引用表和奇怪的预告片条目结束,其第二个修订以一个交叉引用流结束.这是无效的.
幸运的是,了解问题是如何产生的,可以解决这个问题:删除麻烦的预告片条目,例如:
inputBytes = os.toByteArray();
pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
pdDocument.getDocument().getTrailer().removeItem(COSName.TYPE); // <<<<<<<<<< Remove misleading entry <<<<<<<<<<
Run Code Online (Sandbox Code Playgroud)
通过此解决方案,签名文档中的两个修订都使用交叉引用表,并且签名有效.
请注意,如果即将发布的PDFBox版本更改为使用xref流保存从具有交叉引用流的源加载的文档,则必须再次删除解决方法.
不过,我认为在1.xx版本中不会发生这种情况,版本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();
...
Run Code Online (Sandbox Code Playgroud)
(如在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();
...
Run Code Online (Sandbox Code Playgroud)
(如在SignLikeUnOriginalToo.java方法中doSignOneStep)
两种变体明显优于原始方法.
| 归档时间: |
|
| 查看次数: |
911 次 |
| 最近记录: |