【发布时间】:2022-07-07 23:53:14
【问题描述】:
我尝试使用 itext7 签署 pdf 文档,从外部 Web 服务返回的证书和外部签名说签名服务:
我做了以下步骤:
-
得到原始 pdf,添加最后一页(签名页),上面有 2 个签名文件并创建临时 pdf
-
根据创建的临时 pdf 计算哈希
-
与签名服务交换我的 Base64 编码哈希和编码 Base64 签名哈希(我不确定这是原始签名还是 CMS 签名 - 我将其视为 CMS 容器)
-
解码并将获得的签名哈希与来自 Sign Company 的证书一起放入我在临时 pdf 文件上的 Sig 字段之一。我将需要在未来以这种方式签署后续字段。
不幸的是,我在 Adobe Reader 中遇到了验证错误:“自从应用签名后,文档已被更改或损坏”: link to Adobe validation result
在我创建标志页面的代码片段下方:
private void createPdfDocument(Document doc, int iteration) {
//Add last sign page to doc
doc.add(new AreaBreak(AreaBreakType.LAST_PAGE));
doc.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
PdfPage lastPage = doc.getPdfDocument().getLastPage();
float width = lastPage.getPageSize().getWidth();
float height = lastPage.getPageSize().getHeight();
createTitle(doc);
PdfAcroForm form = PdfAcroForm.getAcroForm(doc.getPdfDocument(), true);
for (int i = 1; i <= iteration; i++) {
addSignArea(doc, form, VERTICAL_RECTANGLE_START - (i - 1) * VERTICAL_MARGIN,
VERTICAL_TEXT_START - (i - 1) * VERTICAL_MARGIN, i);
}
System.out.println("Creating sign page finished");
}
private void addSignArea(Document doc, PdfAcroForm form, int verticalRectPosition, int verticalFieldPosition, int iteration) {
Color color = new DeviceRgb(46, 66, 148);
//Create sign area frame
new PdfCanvas(doc.getPdfDocument().getLastPage())
.roundRectangle(50, verticalRectPosition, 495, 50, 5)
.setLineWidth(0.5f)
.setStrokeColor(color)
.stroke();
//Create text fields inside frame
PdfSignatureFormField signField = PdfSignatureFormField.createSignature(doc.getPdfDocument(),
new Rectangle(50, verticalRectPosition, 495, 50));
signField.setFieldName(getFieldCountedName("Signature", iteration));
form.addField(signField);
}
我是这样计算文档哈希的:
public String getDocumentHash() {
try (FileInputStream is = new FileInputStream(DOC)) {
byte[] hash = DigestAlgorithms.digest(is, DigestAlgorithms.SHA256, null);
String encodeToString = Base64.getEncoder().encodeToString(hash);
System.out.println(encodeToString);
return encodeToString;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
最后签署pdf文件:
public class DocumentSigner {
public static final String DEST = "";
private static final String SOURCE = "";
private static final String DOC_HASH = "6XsoKhEXVMu8e0R7BGtaKvghwL0GBrqTGAivFpct6J4=";
public static final String[] RESULT_FILES = new String[]{
"sign_doc_result1.pdf"
};
public static void main(String[] args) throws GeneralSecurityException, IOException {
File file = new File(DEST);
file.mkdirs();
Certificate[] chain = new Certificate[1];
chain[0] = CertLoadTest.getPublicCert(); //load cert from path
String encodedExternalHash = getExternalSignedHash(); //get the signded hash returned from the Sign Service
new DocumentSigner().sign(SOURCE, DEST + RESULT_FILES[0], chain, PdfSigner.CryptoStandard.CMS,
encodedExternalHash, DOC_HASH, "Signature1");
}
public void sign(String src, String dest, Certificate[] chain, PdfSigner.CryptoStandard subfilter,
String encodedExternalHash, String documentHash, String fieldName) throws GeneralSecurityException, IOException {
try (FileOutputStream os = new FileOutputStream(dest); InputStream is = new FileInputStream(src)) {
PdfReader reader = new PdfReader(is);
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
signer.setFieldName(fieldName);
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new CustomSignature(Base64.getDecoder().decode(encodedExternalHash),
Base64.getDecoder().decode(documentHash), chain);
signer.signDetached(digest, signature, chain, null, null, null,
8096, subfilter);
}
}
public class CustomSignature implements IExternalSignature {
private byte[] signedHash;
private byte[] documentHash;
private Certificate[] chain;
public CustomSignature(byte[] signedHash, byte[] documentHash, Certificate[] chain) {
this.signedHash = signedHash;
this.documentHash = documentHash;
this.chain = chain;
}
public String getHashAlgorithm() {
return DigestAlgorithms.SHA256;
}
public String getEncryptionAlgorithm() {
return "RSA";
}
public byte[] sign(byte[] message) throws GeneralSecurityException {
return signedHash;
}
}
private static String getExternalSignedHash() {
//mocked Sign Service result - documentHash is exchanged with signedHash
return "3BLqVMOLSFXEfCy++n0DmRqcfCGCqSLy9Nzpn1IpAn6iTqr+h78+yOomGMAL0La77IB08Tou9gkxbwSXPHrdN5+EPm7HCXeI/z3fzj711H9OH6P9tWtVHgieKUFOVhrm/PTeypSC/vy7RJQLNmL5+/+Moby5Bdo/KaaN2h9Jj41w1i6CwL/7wzCZ0h+AU9sI+IC0i/UbWFFz7VMfN5barcF1vP+ECLiX3qtZrGbFZNZfrr+28ytNTdUR4iZJRLKL2nXeg0CqxsTjnAyUsFMTCro1qv0QkQO8Cv6AJFhWlUFGUkt+pIUKhIticlypB+WdzwmISOsRK0IUiKgrJI6E3g==";
}
A 还尝试将从 Sign Service 哈希返回的结果视为原始签名 - 这是 CustomSignature 类中的 sign 方法的样子:
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, digest, false);
byte[] sh = sgn.getAuthenticatedAttributeBytes(documentHash, PdfSigner.CryptoStandard.CMS, null, null);
sgn.setExternalDigest(signedHash, null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(documentHash, PdfSigner.CryptoStandard.CMS, null, null, null);
return encodedSig;
但在这种情况下,我在 Adobe Reader 中遇到格式签名错误
我的流程是否正确,或者我可能需要另一种方法来正确签署文件。
【问题讨论】:
-
您的流程不正确:在
sign中,您准备要签名的 PDF 并使用您作为参数提供的签名对其进行签名;因此,该签名是在已知要签名的数据之前创建的! (要签名的数据由签名准备完成。) -
请编辑问题以将其限制为具有足够详细信息的特定问题,以确定适当的答案。
-
@mkl 感谢您的评论,经过一番阅读,我认为我应该使用延迟签名过程和预签名,例如这里的示例git.itextsupport.com/projects/I7JS/repos/signatures/browse/src/…
-
这是一种选择。另一种方法是从您的
CustomSignature中调用您的签名服务。哪个更好取决于您的签名服务和整个用例的详细信息。