[英]Using iText (signdeferred) to create PDF digital signature, invalid signature problem appears when verifying signature
I am a Chinese software developer, I am now implementing such a function, using Android client to digitally sign PDF, my implementation is like this我是中国软件开发人员,我现在正在实现这样一个function,使用Android客户端数字签名PDF,我的实现是这样的
https://drive.google.com/file/d/127nVvJ0qtSdG53jM0_GUP-WORYrQ5TBo/view?usp=sharing Now I add the PDF file address, who can help me analyze the problem https://drive.google.com/file/d/127nVvJ0qtSdG53jM0_GUP-WORYrQ5TBo/view?usp=sharing现在我添加PDF文件地址,谁能帮我分析问题
/**
* @return name.ldd.electSign.webservice.pdf.presign.PresignResponseDetail
* @Author 焦康
* @Description 预签章
* @Date 2021/4/25 14:35
* @Param [pdfReader, tempFile, temp, chain, positions, fieldName, calendar, hashName]
**/
public PreSignResponseDetail preSign(PreSignDAO preSignDAO) throws IOException, DocumentException, GeneralSecurityException {
log.debug("执行预签章");
FileOutputStream fileOutputStream = new FileOutputStream(preSignDAO.getTempPath());
// 设置印章信息
preSignDAO.getPdfReader().setAppendable(true);
PdfStamper pdfStamper = PdfStamper.createSignature(preSignDAO.getPdfReader(), fileOutputStream, '\0', null, true);
pdfStamper.getWriter().setCompressionLevel(PdfStream.BEST_COMPRESSION);
PdfSignatureAppearance pdfSignatureAppearance = pdfStamper.getSignatureAppearance();
pdfSignatureAppearance.setReason("");
pdfSignatureAppearance.setLocation("");
pdfSignatureAppearance.setContact("");
pdfSignatureAppearance.setLayer2Text("");
pdfSignatureAppearance.setLayer4Text("");
pdfSignatureAppearance.setSignatureCreator("");
pdfSignatureAppearance.setOpacity(preSignDAO.getSealOpacity());
pdfSignatureAppearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
pdfSignatureAppearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
pdfSignatureAppearance.setCertificate(preSignDAO.getCertificateChain()[0]);
pdfSignatureAppearance.setVisibleSignature(new Rectangle(200, 200, 300, 300), preSignDAO.getPage(), preSignDAO.getFieldName());
// 设置签名图片
//读取图章图片,这个image是itext包的image
Image image = Image.getInstance(preSignDAO.getSealImagePath());
pdfSignatureAppearance.setImage(image);
// 开始预签章
ExternalSignatureContainer externalBlankSignatureContainer = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(pdfSignatureAppearance, externalBlankSignatureContainer, 8192);
// 计算hash
InputStream inputStream = pdfSignatureAppearance.getRangeStream();
BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest();
byte[] hash = DigestAlgorithms.digest(inputStream, bouncyCastleDigest.getMessageDigest(preSignDAO.getHashAlgorithm()));
PdfPKCS7 pkcs7 = new PdfPKCS7(null, preSignDAO.getCertificateChain(), preSignDAO.getHashAlgorithm(), null, bouncyCastleDigest, false);
byte[] sh = pkcs7.getAuthenticatedAttributeBytes(hash, Calendar.getInstance(), null, null, MakeSignature.CryptoStandard.CMS);
String stringSh = new sun.misc.BASE64Encoder().encode(sh);
return new PreSignResponseDetail(preSignDAO.getSourcePath(), preSignDAO.getTempPath(), hash, stringSh.replace("\r\n",""), preSignDAO.getFieldName());
}
/**
* @Author 焦康
* @Description 数据签名
* @Date 2021/4/30 14:57
* @Param [Pin, CN, signData, listener]
* @return void
**/
public void ZAYK_SignData(final String Pin,final String CN,final String signData,HttpsCallbackListener listener){
httpsCallbackListener = listener;
new Thread(new Runnable() {
@Override
public void run() {
String result = null;
try {
byte[] bytes = Base64.decode(signData,Base64.DEFAULT);
result = service.SDZM_SignData(bytes,CN,Pin);
JSONObject jsonObject = JSONObject.parseObject(result);
result = jsonObject.getString("signValue");
Log.e("SDZM_SignData",result);
} catch (Exception e) {
e.printStackTrace();
}
sendMessage(result);
}
}).start();
}
/**
* @return
* @Author 焦康
* @Description 延迟签章
* @Date 2021/4/25 17:01
* @Param
**/
public DeferredSignResponseDetail DeferredSign(DeferredSignDAO deferredSignDAO) {
log.debug("执行延迟签章");
BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest();
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
PdfReader pdfReader = null;
FileOutputStream os = null;
try {
PdfPKCS7 sgn = new PdfPKCS7(null, deferredSignDAO.getCertificateChain(), deferredSignDAO.getHashAlgorithm(), null, bouncyCastleDigest, false);
sgn.setExternalDigest(deferredSignDAO.getSignData(), null, deferredSignDAO.getDigestEncryptionAlgorithm());
byte[] sig = sgn.getEncodedPKCS7(deferredSignDAO.getHashData(), calendar, null, null, null, MakeSignature.CryptoStandard.CMS);
pdfReader = new PdfReader(deferredSignDAO.getTempPath());
os = new FileOutputStream("D:\\ruoyi\\uploadPath\\pdf\\Dest\\输出");
ExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
MakeSignature.signDeferred(pdfReader, deferredSignDAO.getFieldName(), os, external);
} catch (Exception e) {
log.error("延迟签章出错:", e);
} finally {
if (pdfReader != null) {
pdfReader.close();
}
try {
if (os != null) {
os.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return new DeferredSignResponseDetail(deferredSignDAO.getTargetPath() + ".pdf");
}
public class MyExternalSignatureContainer implements ExternalSignatureContainer {
byte[] sig = null;
public MyExternalSignatureContainer(byte[] sig) {
this.sig = sig;
}
@Override
public byte[] sign(InputStream paramInputStream) throws GeneralSecurityException {
return this.sig;
}
@Override
public void modifySigningDictionary(PdfDictionary paramPdfDictionary) {
}
}
When calculating the hash of the to-be-signed attributes, you use the then current time as value of the signing time attribute:在计算待签名属性的 hash 时,使用当时的当前时间作为签名时间属性的值:
PdfPKCS7 pkcs7 = new PdfPKCS7(null, preSignDAO.getCertificateChain(), preSignDAO.getHashAlgorithm(), null, bouncyCastleDigest, false);
byte[] sh = pkcs7.getAuthenticatedAttributeBytes(hash, Calendar.getInstance(), null, null, MakeSignature.CryptoStandard.CMS);
String stringSh = new sun.misc.BASE64Encoder().encode(sh);
Later, when you have received a naked signature for that hash, you re-build the signed attributes using the then current time:稍后,当您收到该 hash 的裸签名时,您使用当时的当前时间重新构建签名属性:
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
...
PdfPKCS7 sgn = new PdfPKCS7(null, deferredSignDAO.getCertificateChain(), deferredSignDAO.getHashAlgorithm(), null, bouncyCastleDigest, false);
sgn.setExternalDigest(deferredSignDAO.getSignData(), null, deferredSignDAO.getDigestEncryptionAlgorithm());
byte[] sig = sgn.getEncodedPKCS7(deferredSignDAO.getHashData(), calendar, null, null, null, MakeSignature.CryptoStandard.CMS);
Thus, the signing time in the signed attributes of the final CMS container differs from the signing time used for hashing, so the signature signs the wrong hash.因此,最终 CMS 容器的已签名属性中的签名时间与用于散列的签名时间不同,因此签名签署了错误的 hash。
To fix this use the identical signing time value in both places!要解决此问题,请在两个地方使用相同的签名时间值!
By the way, if you had used the current iText 5 version, you would not have had that problem: Because that signing time attribute is optional (as long as one sets the signing time in the signature dictionary) and even forbidden for PAdES signatures, the current iText 5 version does not set that attribute anymore and getAuthenticatedAttributeBytes
and getEncodedPKCS7
don't have a Calendar
parameter anymore...顺便说一句,如果您使用的是当前的 iText 5 版本,您就不会遇到这个问题:因为该签名时间属性是可选的(只要在签名字典中设置签名时间)甚至禁止 PAdES 签名,当前的 iText 5 版本不再设置该属性,并且getAuthenticatedAttributeBytes
和getEncodedPKCS7
不再具有Calendar
参数...
As I have not yet dealt with the Chinese algorithms, I cannot tell whether they are applied correctly in your code or in your example file.由于我还没有处理中文算法,我无法判断它们是否在您的代码或示例文件中正确应用。
One warning, though: PdfPKCS7
has been implemented with DSA and RSA in mind;不过,有一个警告: PdfPKCS7
已在考虑 DSA 和 RSA 的情况下实施; X9.62 ECDSA support has been added later, and that with an error (which still is present in iText 7) which Adobe Acrobat ignores but not necessarily other validators;稍后添加了 X9.62 ECDSA 支持,并且带有错误(在 iText 7 中仍然存在),Adobe Acrobat 忽略但不一定是其他验证器; before using PdfPKCS7
for any other algorithms, therefore, you have to verify that that class handles those other algorithms properly, and improve the class if it does not.因此,在将PdfPKCS7
用于任何其他算法之前,您必须验证 class 是否正确处理这些其他算法,如果没有,则改进 class。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.