admin 管理员组文章数量: 1087139
I want to sign a document with using an external API. You can send an hash of your document to that service and get a signed hash back.
This is how I send the document hash to the API:
private String generateHash(byte[] content) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA256");
byte[] hash = digest.digest(content);
return Base64.getEncoder().encodeToString(hash);
}
This is how I handle the response with the signature hash:
byte[] signByte = Base64.getDecoder().decode(stsCallbackDocumentResponse.getSignByte());
After signing the signature is invalid because of "The document was modified or corrupted after the signature was applied".
Here is the code responsible for signing:
private byte[] signPdf(byte[] content, byte[] signByte, String reason, String location)
throws GeneralSecurityException, IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
PdfReader reader = new PdfReader(inputStream);
StampingProperties stampingProperties = new StampingProperties().useAppendMode();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfSigner signer = new PdfSigner(reader, outputStream, stampingProperties);
// .setFieldName("Signature1");
Rectangle rect = new Rectangle(36, 648, 200, 100);
signer.getSignerProperties().setPageRect(rect).setPageNumber(1);
if (reason != null) {
signer.getSignerProperties().setReason(reason);
}
if (location != null) {
signer.getSignerProperties().setLocation(location);
}
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new IExternalSignature() {
@Override
public String getDigestAlgorithmName() {
return DigestAlgorithms.SHA256;
}
@Override
public String getSignatureAlgorithmName() {
return "RSA";
}
@Override
public ISignatureMechanismParams getSignatureMechanismParameters() {
return null;
}
@Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
return signByte;
}
};
StsAccessToken stsAccessToken = stsAccessTokenRepository
.findByApplicationUserId(SecurityUtils.getCurrentUserId());
Certificate[] chain = generateCertificateChain(stsAccessToken.getSigningCertificate().getPemCertificate());
signer.signDetached(digest, signature, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
return outputStream.toByteArray();
}
private Certificate[] generateCertificateChain(String pemCertificate) {
try {
byte[] decodedCert = java.util.Base64.getDecoder()
.decode(pemCertificate.replaceAll("-----\\w+ CERTIFICATE-----", "").replaceAll("\n", ""));
InputStream inputStream = new ByteArrayInputStream(decodedCert);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(inputStream);
List<Certificate> certificateChain = new ArrayList<>();
certificateChain.add(certificate);
return certificateChain.toArray(new Certificate[0]);
} catch (Exception e) {
throw new ServiceException(e);
}
}
Finally I create a document from the resulting byte[].
I also tried saving directly to a document.
PdfSigner signer = new PdfSigner(reader, new FileOutputStream("/home/alex/repo/alex.pdf"), stampingProperties);
Dependencies:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>sign</artifactId>
<version>9.1.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>bouncy-castle-adapter</artifactId>
<version>9.1.0</version>
</dependency>
Any idea what I'm doing wrong?
I don't have API documentation for the signing step, I only have one link.
UPDATES:
You are right, I was not sending the hash for the message from the sign method of IExternalSignature but was sending the hash of the entire document because that's what the API documentation says.
What I didn't mention and I think it was useful to do is that my signature happens in different steps.
My poor documentation says:
1. /api/v1/signature
Body:
- id: unique identifier of the record.
- hashByte: the hash value of the document, encoded (e.g. Base64) to validate data integrity.
- algorithmName: name of the algorithm used to generate the hash (e.g. SHA256).
- docName: name of the document or file for which the hash was calculated.
2. /api/v1/callback
Response:
- id: unique identifier of the record.
- signByte: A digital signature in the form of an array of bytes, encoded in Base64.
I updated the code as follows after your response but the signature is still invalid.
public void signDocument(DocumentContent documentContent) {
/*
* {
* "id": documentContent.getId(),
* "hashByte": generateHash(documentContent.getContent()),
* "algorithmName": "SHA256",
* "docName": documentContent.getName()
* }
* send sign request
*/
}
private String generateHash(byte[] content) {
try {
PdfReader reader = new PdfReader(new ByteArrayInputStream(content));
PdfSigner signer = new PdfSigner(reader, new ByteArrayOutputStream(),
new StampingProperties().useAppendMode());
signer.getSignerProperties().setPageRect(new Rectangle(36, 648, 200, 100)).setPageNumber(1);
final HashHolder extracted = new HashHolder();
IExternalSignature hashExtractor = new IExternalSignature() {
@Override
public byte[] sign(byte[] hash) throws GeneralSecurityException {
extracted.hash = hash;
return new byte[0];
}
@Override
public String getDigestAlgorithmName() {
return DigestAlgorithms.SHA256;
}
@Override
public String getSignatureAlgorithmName() {
return "RSA";
}
@Override
public ISignatureMechanismParams getSignatureMechanismParameters() {
return null;
}
};
signer.signDetached(new BouncyCastleDigest(), hashExtractor, getCertificateChain(), null, null, null, 0,
PdfSigner.CryptoStandard.CMS);
MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
byte[] hash = messageDigest.digest(extracted.hash);
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new ServiceException(e);
}
}
public void addCallbackSignatureHash(DocumentContent documentContent) {
try {
byte[] signByte = Base64.getDecoder().decode(getCallbackSignByte());
PdfReader reader = new PdfReader(new ByteArrayInputStream(documentContent.getContent()));
PdfSigner signer = new PdfSigner(reader, new FileOutputStream("/home/alex/repo/alex.pdf"),
new StampingProperties().useAppendMode());
signer.getSignerProperties().setPageRect(new Rectangle(36, 648, 200, 100)).setPageNumber(1);
IExternalSignature signature = new IExternalSignature() {
@Override
public String getDigestAlgorithmName() {
return DigestAlgorithms.SHA256;
}
@Override
public String getSignatureAlgorithmName() {
return "RSA";
}
@Override
public ISignatureMechanismParams getSignatureMechanismParameters() {
return null;
}
@Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
return signByte;
}
};
signer.signDetached(new BouncyCastleDigest(), signature, getCertificateChain(), null, null, null, 0,
PdfSigner.CryptoStandard.CMS);
} catch (Exception e) {
throw new ServiceException(e);
}
}
What am I missing this time?
I want to sign a document with using an external API. You can send an hash of your document to that service and get a signed hash back.
This is how I send the document hash to the API:
private String generateHash(byte[] content) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA256");
byte[] hash = digest.digest(content);
return Base64.getEncoder().encodeToString(hash);
}
This is how I handle the response with the signature hash:
byte[] signByte = Base64.getDecoder().decode(stsCallbackDocumentResponse.getSignByte());
After signing the signature is invalid because of "The document was modified or corrupted after the signature was applied".
Here is the code responsible for signing:
private byte[] signPdf(byte[] content, byte[] signByte, String reason, String location)
throws GeneralSecurityException, IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
PdfReader reader = new PdfReader(inputStream);
StampingProperties stampingProperties = new StampingProperties().useAppendMode();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfSigner signer = new PdfSigner(reader, outputStream, stampingProperties);
// .setFieldName("Signature1");
Rectangle rect = new Rectangle(36, 648, 200, 100);
signer.getSignerProperties().setPageRect(rect).setPageNumber(1);
if (reason != null) {
signer.getSignerProperties().setReason(reason);
}
if (location != null) {
signer.getSignerProperties().setLocation(location);
}
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new IExternalSignature() {
@Override
public String getDigestAlgorithmName() {
return DigestAlgorithms.SHA256;
}
@Override
public String getSignatureAlgorithmName() {
return "RSA";
}
@Override
public ISignatureMechanismParams getSignatureMechanismParameters() {
return null;
}
@Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
return signByte;
}
};
StsAccessToken stsAccessToken = stsAccessTokenRepository
.findByApplicationUserId(SecurityUtils.getCurrentUserId());
Certificate[] chain = generateCertificateChain(stsAccessToken.getSigningCertificate().getPemCertificate());
signer.signDetached(digest, signature, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
return outputStream.toByteArray();
}
private Certificate[] generateCertificateChain(String pemCertificate) {
try {
byte[] decodedCert = java.util.Base64.getDecoder()
.decode(pemCertificate.replaceAll("-----\\w+ CERTIFICATE-----", "").replaceAll("\n", ""));
InputStream inputStream = new ByteArrayInputStream(decodedCert);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(inputStream);
List<Certificate> certificateChain = new ArrayList<>();
certificateChain.add(certificate);
return certificateChain.toArray(new Certificate[0]);
} catch (Exception e) {
throw new ServiceException(e);
}
}
Finally I create a document from the resulting byte[].
I also tried saving directly to a document.
PdfSigner signer = new PdfSigner(reader, new FileOutputStream("/home/alex/repo/alex.pdf"), stampingProperties);
Dependencies:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>sign</artifactId>
<version>9.1.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>bouncy-castle-adapter</artifactId>
<version>9.1.0</version>
</dependency>
Any idea what I'm doing wrong?
I don't have API documentation for the signing step, I only have one link.
UPDATES:
You are right, I was not sending the hash for the message from the sign method of IExternalSignature but was sending the hash of the entire document because that's what the API documentation says.
What I didn't mention and I think it was useful to do is that my signature happens in different steps.
My poor documentation says:
1. /api/v1/signature
Body:
- id: unique identifier of the record.
- hashByte: the hash value of the document, encoded (e.g. Base64) to validate data integrity.
- algorithmName: name of the algorithm used to generate the hash (e.g. SHA256).
- docName: name of the document or file for which the hash was calculated.
2. /api/v1/callback
Response:
- id: unique identifier of the record.
- signByte: A digital signature in the form of an array of bytes, encoded in Base64.
I updated the code as follows after your response but the signature is still invalid.
public void signDocument(DocumentContent documentContent) {
/*
* {
* "id": documentContent.getId(),
* "hashByte": generateHash(documentContent.getContent()),
* "algorithmName": "SHA256",
* "docName": documentContent.getName()
* }
* send sign request
*/
}
private String generateHash(byte[] content) {
try {
PdfReader reader = new PdfReader(new ByteArrayInputStream(content));
PdfSigner signer = new PdfSigner(reader, new ByteArrayOutputStream(),
new StampingProperties().useAppendMode());
signer.getSignerProperties().setPageRect(new Rectangle(36, 648, 200, 100)).setPageNumber(1);
final HashHolder extracted = new HashHolder();
IExternalSignature hashExtractor = new IExternalSignature() {
@Override
public byte[] sign(byte[] hash) throws GeneralSecurityException {
extracted.hash = hash;
return new byte[0];
}
@Override
public String getDigestAlgorithmName() {
return DigestAlgorithms.SHA256;
}
@Override
public String getSignatureAlgorithmName() {
return "RSA";
}
@Override
public ISignatureMechanismParams getSignatureMechanismParameters() {
return null;
}
};
signer.signDetached(new BouncyCastleDigest(), hashExtractor, getCertificateChain(), null, null, null, 0,
PdfSigner.CryptoStandard.CMS);
MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
byte[] hash = messageDigest.digest(extracted.hash);
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new ServiceException(e);
}
}
public void addCallbackSignatureHash(DocumentContent documentContent) {
try {
byte[] signByte = Base64.getDecoder().decode(getCallbackSignByte());
PdfReader reader = new PdfReader(new ByteArrayInputStream(documentContent.getContent()));
PdfSigner signer = new PdfSigner(reader, new FileOutputStream("/home/alex/repo/alex.pdf"),
new StampingProperties().useAppendMode());
signer.getSignerProperties().setPageRect(new Rectangle(36, 648, 200, 100)).setPageNumber(1);
IExternalSignature signature = new IExternalSignature() {
@Override
public String getDigestAlgorithmName() {
return DigestAlgorithms.SHA256;
}
@Override
public String getSignatureAlgorithmName() {
return "RSA";
}
@Override
public ISignatureMechanismParams getSignatureMechanismParameters() {
return null;
}
@Override
public byte[] sign(byte[] message) throws GeneralSecurityException {
return signByte;
}
};
signer.signDetached(new BouncyCastleDigest(), signature, getCertificateChain(), null, null, null, 0,
PdfSigner.CryptoStandard.CMS);
} catch (Exception e) {
throw new ServiceException(e);
}
}
What am I missing this time?
Share Improve this question edited Mar 28 at 12:26 Alex Dolhescu asked Mar 27 at 19:09 Alex DolhescuAlex Dolhescu 112 bronze badges 7 | Show 2 more comments1 Answer
Reset to default 1As already indicated in a comment, it is unclear from your question which bytes you apply your generateHash
method to and then sign. Apparently, though, you don't apply it to the byte[] message
parameter of the sign
method of your IExternalSignature
implementation as you should.
Thus, basically you should simply change that sign
method to something like this:
public byte[] sign(byte[] message) throws GeneralSecurityException {
MessageDigest digest = MessageDigest.getInstance("SHA256");
byte[] hash = digest.digest(message);
String base64Hash = Base64.getEncoder().encodeToString(hash);
var stsCallbackDocumentResponse = [... request signature response for base64Hash ...];
byte[] signByte = Base64.getDecoder().decode(stsCallbackDocumentResponse.getSignByte());
return signByte;
}
and get rid of the signByte
parameter of signPdf
.
With that change in place, signPdf
should be able to properly sign your PDF in a single step.
Yes, there are situations in which people use a multistep approach to signing with iText, i.e. you first sign the PDF with a dummy signature value, calculate the hash of the signed byte ranges of that dummy-signed file, request and retrieve a signature for that hash, and overwrite the dummy signature in the prepared PDF by the actual one.
Often, though, they do this without need and only create unnecessarily complicated code this way. The only situations in which such a multistep approach makes sense for me, are either when the signature value retrieval from a server takes a long time and you don't want your thread to linger that long; or when the preparation step and the signature value retrieval occur on different computers, for example the preparation step is executed on a computer without network access to the signing server.
本文标签:
版权声明:本文标题:java - Itext 9 - Signing a PDF using an external service - "document was modified or corrupted after the signature was 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/p/1744072332a2528717.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
generateHash
to? Apparently not themessage
parameter bytes of thesign
method of yourIExternalSignature
implementation as you should do. – mkl Commented Mar 28 at 6:07