使用PDFBox 2.0.12的外部签名

huangapple go评论59阅读模式
英文:

External Signature with PDFBox 2.0.12

问题

Signing modified issue.

使用DSC令牌(外部签名)传递文档哈希和签名哈希。

出现错误:“文档在签名后已被更改或损坏”
获取文档的哈希值:

英文:

Signing modified issue.

Iam passing the doc hash and signing hash using a DSC token(External Signing).

iam getting error like:"The document has been altered or corrupted since it was signed"
Getting the Hash of Document:-

public String genrateDigitalCertificateSign() {
		try {
			src = new FileInputStream(inputFilePath);
			OutputStream dest = new FileOutputStream(new File(RESULT_FOLDER, "Test.pdf"));
			pdDocument = PDDocument.load(src);

			PDSignature pds = null;
			String hashdocument = null;
			File imgFile = new File(inputImgPath);
			PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
			if (acroForm == null) {
				pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
			}
			acroForm.setSignaturesExist(true);
			acroForm.setAppendOnly(true);
			acroForm.getCOSObject().setDirect(true);

			pds = new PDSignature();
			pds.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
			pds.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
			pds.setSignDate(Calendar.getInstance());

			PDPage pdPage = pdDocument.getPage(0);
			PDImageXObject pdImage = PDImageXObject.createFromFileByContent(imgFile, pdDocument);

			//visible signature rectangle
			rectangle = new PDRectangle(200.00, 200.00,150.00,50.00);
			
			List<PDField> acroFormFields = acroForm.getFields();
			PDSignatureField signatureField = new PDSignatureField(acroForm);
			acroForm.setSignaturesExist(true);
			acroForm.setAppendOnly(true);
			acroForm.getCOSObject().setDirect(true);
			signatureField.setValue(pds);
			acroFormFields.add(signatureField);
			pdDocument.addSignature(pds);
			
			//creating visible stamp
			createVisualSignatureTemplate(pdDocument, signatureField, pdPage, rectangle, pdImage, signDisplayInfo);

			externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
			InputStream dataToSign = externalSigning.getContent();
			hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
			return hashdocument;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

External signing Code:-

public byte[] sign(byte[] hash)
			throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
		PrivateKey privKey = pk;
		try {
			List<Certificate> certList = new ArrayList<>();
			certList.addAll(Arrays.asList(chain));
			Store certs = new JcaCertStore(certList);
			CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
			org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate
					.getInstance(chain[0].getEncoded());
			ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privKey);
			gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
							.build(sha1Signer, new X509CertificateHolder(cert)));
			gen.addCertificates(certs);
			CMSProcessableInputStream msg = new CMSProcessableInputStream(new ByteArrayInputStream(hash));
			CMSSignedData signedData = gen.generate(msg, false);
			return signedData.getEncoded();
		} catch (GeneralSecurityException e) {
			throw new IOException(e);
		} catch (CMSException e) {
			throw new IOException(e);
		} catch (OperatorCreationException e) {
			throw new IOException(e);
		}
	}

Signature appending code:-

public void signedPDF(byte[] hash)
			throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException {
		byte[] signedHash = sign(hash);
		externalSigning.setSignature(signedHash);
		IOUtils.closeQuietly(src);
		pdDocument.close();
	} 

After this signature is appending,but while opening the signed PDF getting the error like as shown in the below image.

使用PDFBox 2.0.12的外部签名

PDF file Link:
https://drive.google.com/file/d/1qRT2CVgET8Ds1fu0b5psii3j8ytPKaLH/view?usp=sharing

[EDITED]

public byte[] genrateDigitalCertificateSign() {
		try {
			src = new FileInputStream(inputFilePath);
			OutputStream dest = new FileOutputStream(new File(RESULT_FOLDER, "Test.pdf"));
			pdDocument = PDDocument.load(src);

			PDSignature pds = null;
			String hashdocument = null;
			File imgFile = new File(inputImgPath);
			PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
			if (acroForm == null) {
				pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
			}
			acroForm.setSignaturesExist(true);
			acroForm.setAppendOnly(true);
			acroForm.getCOSObject().setDirect(true);

			pds = new PDSignature();
			pds.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
			pds.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
			pds.setSignDate(Calendar.getInstance());

			PDPage pdPage = pdDocument.getPage(0);
			PDImageXObject pdImage = PDImageXObject.createFromFileByContent(imgFile, pdDocument);

			//visible signature rectangle
			rectangle = new PDRectangle(200.00, 200.00,150.00,50.00);
			
			List<PDField> acroFormFields = acroForm.getFields();
			PDSignatureField signatureField = new PDSignatureField(acroForm);
			acroForm.setSignaturesExist(true);
			acroForm.setAppendOnly(true);
			acroForm.getCOSObject().setDirect(true);
			signatureField.setValue(pds);
			acroFormFields.add(signatureField);
			pdDocument.addSignature(pds);
			
			//creating visible stamp
			createVisualSignatureTemplate(pdDocument, signatureField, pdPage, rectangle, pdImage, signDisplayInfo);

			externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
			InputStream dataToSign = externalSigning.getContent();
			hashdocument = DigestUtils.sha256(dataToSign); // hash is generated
			return hashdocument;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

Recently signed PDF file link:-
https://drive.google.com/file/d/1mZ8Wqppx3EylI1aLYB9Fl8NzibBXZxzR/view?usp=sharing

[Edited 2] this is the flow i tried after your suggestions.
In this what is the 1st issue your pointing out? Iam unable to figure out, please help.

externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
			InputStream dataToSign = externalSigning.getContent();
			hashdocument = DigestUtils.sha256(dataToSign); // hash is generated 
			byte[] signedhash = sign(hashdocument);
			externalSigning.setSignature(signedhash);
			pdDocument.close();

答案1

得分: 3

以下是翻译好的部分:

双重散列

你的代码存在两个问题:

你的代码创建了一个包含文档数据的哈希的哈希的签名,而实际上只期望文档数据的哈希。

你的代码主要是从PDFBox示例中借用的。在外部签名的情况下,会检索并将“content to sign”转发到“sign”方法:

byte[] cmsSignature = sign(externalSigning.getContent());

(来自扩展了CreateSignatureBaseCreateSignature

然而,在你的代码中,你首先对要签名的内容进行哈希处理,然后将该哈希传递给sign方法:

public String genrateDigitalCertificateSign() {
    ...
    InputStream dataToSign = externalSigning.getContent();
    hashdocument = DigestUtils.sha256Hex(dataToSign); // 生成哈希
    return hashdocument;
    ...
}

byte[] signedHash = sign(hash);

就CMS签名容器的创建而言,你的sign方法使用了与PDFBox示例相同的代码。

因此,你的代码对文档数据进行了多次哈希处理。要解决这个问题,你可以:

  • 要么删除哈希处理步骤,直接将文档数据传递给签名
  • 要么更改sign方法,不再对其输入进行哈希处理。

我假设你的意图是计算并将哈希传递给一个独立的签名服务,所以你的选择可能是后者。

十六进制编码哈希

你的哈希生成方法genrateDigitalCertificateSign返回的哈希值不是实际的byte[],而是对其进行了十六进制编码,并返回了十六进制字符串:

hashdocument = DigestUtils.sha256Hex(dataToSign); // 生成哈希
return hashdocument;

然而,你的其他方法期望获得并操作实际的byte[]

要解决这个问题,你可以:

  • 要么一开始不进行十六进制编码,即使用DigestUtils.sha256而不是DigestUtils.sha256Hex,并从genrateDigitalCertificateSign返回一个byte[]而不是一个String
  • 要么在将哈希值传递给signedPDF方法的sign方法之前解码十六进制哈希值。

我假设你的意图是以字符串形式传输哈希值,所以你的选择可能是后者。

英文:

Ok, there are two issues in your code:

Double Hashing

Your code creates a signature that contains the hash of the hash of the document data where simply the hash of the document data is expected.

Your code mostly is borrowed from the PDFBox examples. In case of external signing the content to sign is retrieved and forwarded to the sign method:

byte[] cmsSignature = sign(externalSigning.getContent());

(from CreateSignature which extends CreateSignatureBase)

In your code, though, you first hash the content to sign and forward that hash to the sign method:

public String genrateDigitalCertificateSign() {
    ...
    InputStream dataToSign = externalSigning.getContent();
    hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
    return hashdocument;
    ...
}

byte[] signedHash = sign(hash);

As fas as creation of the CMS signature container is concerned, your sign method uses the same code as the PDFBox example.

Thus, your code hashes the document data once too often. To fix this, you have to

  • either drop the hashing step and forward the document data to sign
  • or change the sign method to not hash its input again.

I assume your intention is to calculate and forward the hash to a separate signing service, so your choice would be the latter.

Hex Encoding the Hash

Your hash generating method genrateDigitalCertificateSign returns the hash value not as the actual byte[] but hex encodes it and returns that hex string:

hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
return hashdocument;

Your other methods, though, expect to get and operate on the actual byte[].

To fix this, you have to

  • either not hex encode to start with, i.e. use DigestUtils.sha256 instead of DigestUtils.sha256Hex and return a byte[] instead of a String from genrateDigitalCertificateSign,
  • or hex decode the hash value before feeding it to the sign method in your signedPDF method.

I assume your intention is to transport the hash in string form, so your choice would be the latter.

huangapple
  • 本文由 发表于 2020年7月21日 23:05:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/63017494.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定