The example file
You shared an example file signed by your code in a comment to your question.
An analysis of that file (executed using the AnalyzeSignatures test testPriyankaSignatureSampleinformedconsent_Signed
) shows that the actually signed hash is
1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B
which should be the hash of the authenticated attributes but their hash is
E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.
The signed byte ranges hash is
D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,
so its not this hash, either. Thus it was not clear where that signed hash came from.
It eventually turned out that
the encoding utility on web service was different due to which the decoded hash value was wrong and MessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);)
is not needed. this line was giving the wrong hash.
Signing previously signed files
Thereafter a new issue popped up:
when I am signing file which is already signed is getting sign but with an invalid signature. its showing "the signature byte range is invalid".
For signing already signed pdfs you have to use the append mode. For that you need a different PdfStamper.createSignature
overload with one more parameter, a boolean
, which you set to true
.
The reason is that usually (i.e. without activating the append mode) iText reorganizes the internal PDF structures and drops unused objects. In already signed PDFs this usually moves the position of the (already existing) signature which invalidates the signature structure. Using append mode, though, iText keeps the original PDF bytes as they were and only appends new data.
Changing hashes
I m just confused that why the value of byte "sh" is getting change for the same file every time? actually, I want to store the hash value of the file. is it because of "cal"?
Indeed, each time you start manipulating a PDF, the result gets a new unique ID. Furthermore, the modification time is stored. And in case of a signing use case, also the signing time differs.
can I get the hash at a time and store it in db and sign the hash later and append in pdf?
You either have to
- keep the stamper and appearance open for the time, or you have to
- keep the prepared file, or you have to
- patch iText to allow you to set fixed values for signing time, manipulation time, and id.
1st option is not possible for me. I didn't get the 2nd 3rd option. can you elaborate both or give any reference which I can refer?
Patching iText to enforce fixed hashes for each file
Ok, first off the third option, patching iText, usually is something you don't want to do because it makes incorporating later iText updates difficult.
OpenPdf (an older iText fork) contains a patch adding EnforcedModificationDate
, OverrideFileId
, and IncludeFileID
properties to PdfStamper
. (PdfSignatureAppearance
already has a SignDate
property.) This patch has been applied to allow eSignature DSS to use OpenPdf in its signing process (which also includes creating the signed PDF twice and so requires fixed hash values).
You might not want to switch to this old iText fork because it misses many fixes and new options of newer iText versions.
Keeping the already prepared file
Thus, you probably should instead keep the originally created file with an empty signature, e.g. in some temporary folder, and apply deferred signing as soon as you retrieve the final signature.
This essentially is what the iText example C4_09_DeferredSigning is all about, it first creates an intermediary PDF with everything in it, only the signatrue bytes are missing:
public void emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
appearance.setCertificate(chain[0]);
ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(appearance, external, 8192);
}
Only in a second step an actual signature is injected:
public void createSignature(String src, String dest, String fieldname, PrivateKey pk, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(pk, chain);
MakeSignature.signDeferred(reader, fieldname, os, external);
}
using
class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected PrivateKey pk;
protected Certificate[] chain;
public MyExternalSignatureContainer(PrivateKey pk, Certificate[] chain) {
this.pk = pk;
this.chain = chain;
}
public byte[] sign(InputStream is) throws GeneralSecurityException {
try {
PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", "BC");
String hashAlgorithm = signature.getHashAlgorithm();
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
byte[] extSignature = signature.sign(sh);
sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
return sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
}
catch (IOException ioe) {
throw new ExceptionConverter(ioe);
}
}
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
Here the has is calculated in the second step, but you don't need to do so, you can
- already calculate it in the first step by not using the original
ExternalBlankSignatureContainer
as in emptySignature()
above but instead extending it to calculate the hash as MyExternalSignatureContainer
does,
- storing this value in your database, and
- (as soon as you retrieve the signature) injecting that signature using a variant of
MyExternalSignatureContainer
that does not calculate the hash but instead injects exactly the returned signature.
1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B
which should be the hash of the authenticated attributes but their hash isE7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949
. The signed byte ranges hash isD7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28
, so its not this hash, either. So I don't really know the hash of what that11...
is. You might want to share more code of what happens betweenString hashSignat = hashPdf
andsgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA")
... – mkl