使用 IText 在 pdf 文件中多次签名
Multiple Signings in pdf File using IText
我想用多个签名签署 pdf,但我只能用一个签署 pdf。
我正在使用 Itext 库。
public static void sign(InputStream src,OutputStream dest, InputStream p12Stream, char[] password, String reason, String location, String imagePath) throws Exception {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(p12Stream, password);
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
Certificate[] chain = ks.getCertificateChain(alias);
PdfReader reader = new PdfReader(src);
PdfStamper stamper = PdfStamper.createSignature(reader, dest, '[=10=]', null, true);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setVisibleSignature(new Rectangle(300, 600, 630, 500), 1, "sig");
Image image = Image.getInstance(imagePath);
appearance.setSignatureGraphic(image);
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
ExternalDigest digest = new BouncyCastleDigest();
ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, null);
MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
}
public static void main(String[] args) throws Exception {
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
}
我已经尝试使用附加模式为 true 并删除图像签名,但它只显示为一个签名。
image showing pdf signed
你的方法有很多错误
你的main
方法
原始main
代码
在您的原始代码中,您的 main
方法包含以下两个调用:
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"nonsigned.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
特别是在您的第一行中,您为相同的文件名创建了一个 FileInputStream
和一个 FileOutputStream
。执行后者 t运行 对文件进行分类,因此当 sign
方法中的代码尝试从前一个流中读取文件时,它找不到任何内容并抛出异常。
因此,您不能使用同一个文件作为签名过程的输入和输出。
编辑后的 main
代码
然后您编辑了 main
方法以包含这两个调用:
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
现在两个调用都读取 未签名 PDF,对其进行签名,并将结果写入同一个输出文件。因此,第一次调用创建了一个具有单一签名的 PDF,第二次调用也创建了一个具有单一签名的 PDF,并且其输出覆盖了第一次调用的输出。
因此,您必须使用第一个签名调用的输出作为第二个签名调用的输入。
例如像这样:
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signedOnce.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"signedOnce.pdf"), new FileOutputStream(basePath+"signedTwice.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
你的sign
方法
确保没有签名调用使用相同的文件作为输入和输出,并且第二次签名调用使用第一次调用的输出作为输入,我们现在 运行 进入您的 sign
方法多次使用。 (一次性使用即可。)
签名字段名称
在您的 sign
方法中,您对签名字段名称进行了硬编码:
appearance.setVisibleSignature(new Rectangle(300, 600, 630, 500), 1, "sig");
即每次调用都尝试在 相同的 签名字段上签名。根据您使用的 setVisibleSignature
方法的 JavaDocs,但是:
/**
* Sets the signature to be visible. It creates a new visible signature field.
* @param pageRect the position and dimension of the field in the page
* @param page the page to place the field. The fist page is 1
* @param fieldName the field name or <CODE>null</CODE> to generate automatically a new field name
*/
public void setVisibleSignature(Rectangle pageRect, int page, String fieldName)
因此此方法尝试创建一个新的 可见签名字段。由于每个字段都有不同的名称,因此两次使用相同的名称是错误的。
因此,您必须确保使用不同的签名字段名称。 fieldName
参数的 JavaDoc 描述中描述了一个简单的选项:如果你使用 null
,iText 会自动创建一个新的签名名称。因此,只需将上面代码行中的 "sig"
替换为 null
。
认证签名
在您的 sign
方法中,您设置的认证级别如下:
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
这会导致两个问题:
CERTIFIED_NO_CHANGES_ALLOWED
意思是:不允许 更改。 第二次签名 是一个变化。所以使用这个认证级别禁止再次签名。有关允许和不允许更改已签名 PDF 的详细信息,请阅读 this answer。
因此,您必须使用不禁止创建第二个签名的认证级别。
一个文档只能包含一个认证签名和任意数量的批准签名(没有认证级别的常规签名)。
因此,您必须确保只为您对相关文档的第一次签名调用设置签名外观认证级别。
我想用多个签名签署 pdf,但我只能用一个签署 pdf。 我正在使用 Itext 库。
public static void sign(InputStream src,OutputStream dest, InputStream p12Stream, char[] password, String reason, String location, String imagePath) throws Exception {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(p12Stream, password);
String alias = ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
Certificate[] chain = ks.getCertificateChain(alias);
PdfReader reader = new PdfReader(src);
PdfStamper stamper = PdfStamper.createSignature(reader, dest, '[=10=]', null, true);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setVisibleSignature(new Rectangle(300, 600, 630, 500), 1, "sig");
Image image = Image.getInstance(imagePath);
appearance.setSignatureGraphic(image);
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
ExternalDigest digest = new BouncyCastleDigest();
ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, null);
MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
}
public static void main(String[] args) throws Exception {
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
}
我已经尝试使用附加模式为 true 并删除图像签名,但它只显示为一个签名。 image showing pdf signed
你的方法有很多错误
你的main
方法
原始main
代码
在您的原始代码中,您的 main
方法包含以下两个调用:
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"nonsigned.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
特别是在您的第一行中,您为相同的文件名创建了一个 FileInputStream
和一个 FileOutputStream
。执行后者 t运行 对文件进行分类,因此当 sign
方法中的代码尝试从前一个流中读取文件时,它找不到任何内容并抛出异常。
因此,您不能使用同一个文件作为签名过程的输入和输出。
编辑后的 main
代码
然后您编辑了 main
方法以包含这两个调用:
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
现在两个调用都读取 未签名 PDF,对其进行签名,并将结果写入同一个输出文件。因此,第一次调用创建了一个具有单一签名的 PDF,第二次调用也创建了一个具有单一签名的 PDF,并且其输出覆盖了第一次调用的输出。
因此,您必须使用第一个签名调用的输出作为第二个签名调用的输入。
例如像这样:
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signedOnce.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"signedOnce.pdf"), new FileOutputStream(basePath+"signedTwice.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
你的sign
方法
确保没有签名调用使用相同的文件作为输入和输出,并且第二次签名调用使用第一次调用的输出作为输入,我们现在 运行 进入您的 sign
方法多次使用。 (一次性使用即可。)
签名字段名称
在您的 sign
方法中,您对签名字段名称进行了硬编码:
appearance.setVisibleSignature(new Rectangle(300, 600, 630, 500), 1, "sig");
即每次调用都尝试在 相同的 签名字段上签名。根据您使用的 setVisibleSignature
方法的 JavaDocs,但是:
/**
* Sets the signature to be visible. It creates a new visible signature field.
* @param pageRect the position and dimension of the field in the page
* @param page the page to place the field. The fist page is 1
* @param fieldName the field name or <CODE>null</CODE> to generate automatically a new field name
*/
public void setVisibleSignature(Rectangle pageRect, int page, String fieldName)
因此此方法尝试创建一个新的 可见签名字段。由于每个字段都有不同的名称,因此两次使用相同的名称是错误的。
因此,您必须确保使用不同的签名字段名称。 fieldName
参数的 JavaDoc 描述中描述了一个简单的选项:如果你使用 null
,iText 会自动创建一个新的签名名称。因此,只需将上面代码行中的 "sig"
替换为 null
。
认证签名
在您的 sign
方法中,您设置的认证级别如下:
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
这会导致两个问题:
CERTIFIED_NO_CHANGES_ALLOWED
意思是:不允许 更改。 第二次签名 是一个变化。所以使用这个认证级别禁止再次签名。有关允许和不允许更改已签名 PDF 的详细信息,请阅读 this answer。因此,您必须使用不禁止创建第二个签名的认证级别。
一个文档只能包含一个认证签名和任意数量的批准签名(没有认证级别的常规签名)。
因此,您必须确保只为您对相关文档的第一次签名调用设置签名外观认证级别。