问题
I try to sign a pdf file using my smartcard (USB token) but encounter "Document has been altered or corrupted since it was signed"
error when I open the signed pdf file in Adobe. The error is not so descriptive and I'm not sure where to look at because the code seems good to me but apparently it's not..
The code that I use is:
var signer = smartCardManager.getSigner("myTokenPassword");
var toBeSignedHash = GetHashOfPdf(File.ReadAllBytes(@"xxx\pdf.pdf"), cert.asX509Certificate2().RawData, "dsa", null, false);
var signature = signer.sign(toBeSignedHash);
var signedPdf = EmbedSignature(cert.getBytes(), signature);
File.WriteAllBytes(@"xxx\signedpdf.pdf", signedPdf);
public byte[] GetHashOfPdf(byte[] unsignedFile, byte[] userCertificate, string signatureFieldName, List<float> location, bool append)
{
byte[] result = null;
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(userCertificate))
};
Org.BouncyCastle.X509.X509Certificate certificate = chain.ElementAt(0);
using (PdfReader reader = new PdfReader(unsignedFile))
{
using (var os = new MemoryStream())
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0', null, append);
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(0,0,0,0), 1, signatureFieldName);
appearance.Certificate = certificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
Stream data = appearance.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(data, "SHA256");
var signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
result = DigestAlgorithms.Digest(new MemoryStream(signatureHash), "SHA256");
this.hash = hash;
this.os = os.ToArray();
File.WriteAllBytes(@"xxx\temp.pdf", this.os);
}
}
return result;
}
public byte[] EmbedSignature(byte[] publicCert, byte[] sign)
{
var chain = new List<Org.BouncyCastle.X509.X509Certificate>
{
Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(publicCert))
};
var signatureContainer = new PdfPKCS7(null, chain, "SHA256", false);
using (var reader = new PdfReader(this.os))
{
using (var os2 = new MemoryStream())
{
signatureContainer.SetExternalDigest(sign, null, "RSA");
byte[] encodedSignature = signatureContainer.GetEncodedPKCS7(this.hash, null, null, null, CryptoStandard.CMS);
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSignature);
MakeSignature.SignDeferred(reader, "dsa", os2, external);
return os2.ToArray();
}
}
}
The pdf file that I try to sign is this.
Temp pdf file that is created after adding signature fields is this.
Signed pdf file is this.
Base64 format of the hash that is signed is: klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
Base64 format of the signature is: Uam/J6W0YX99rVP4M9mL9Lg9l6YzC2yiR4OtJ18AH1PtBVaNPteT3oPS7SUc+6ak2LfijgJ6j1RgdLamodDPKl/0E90kbBenry+/g1Ttd1bpO8lqTn1PWJU2TxeGHwyRyaFBOUga2AxpErIHrwxfuKCBcodB7wvAqRjso0jovnyP/4DluyOPm97QWh4na0S+rtUWOdqVmKGOuGJ3sBXuk019ewpvFcqWBX4Mvz7IKV56wcxQVQuJLCiyXsMXoazwyDCvdteaDz05K25IVwgEEjwLrppnc/7Ue9a9KVadFFzXWXfia7ndmUCgyd70r/Z+Oviu9MIAZL8GuTpkD7fJeA==
回答1:
I use hex encoding of byte arrays here. Your base64 encoded hash
klh6CGp7DUzayt62Eusiqjr1BFCcTZT4XdgnMBq7QeY=
in hex encoding is equal to
92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
In short
Your code hashes the signed attributes twice. Simply don't hash the bytes returned by signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS)
in GetHashOfPdf
but instead use the authenticated attribute bytes themselves as return value.
In detail
Analyzing the signature in your example PDF it turns out that
indeed the hash of the signed attributes is
92587A086A7B0D4CDACADEB612EB22AA3AF504509C4D94F85DD827301ABB41E6
but the hash in the RSA encrypted
DigestInfo
object of the signature is1DC7CAA50D88243327A9D928D5FB4F1A61CBEFF9E947D393DDA705BD61B67F25
which turns out to be the hash of the before mentioned hash of the signed attributes.
Thus, your
var signature = signer.sign(toBeSignedHash);
call appears to hash the toBeSignedHash
value again.
The most simple fix would be to replace
byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
result = DigestAlgorithms.Digest(new MemoryStream(signatureHash), "SHA256");
by
result = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
in GetHashOfPdf
to have only signer.sign
do the hashing.
Analyzing such issues
In a comment you asked
how did you figure all this out :)?
Well, yours is not the first question with a customized iText signing process resulting in errors or at least unwanted profiles.
In the course of the analysis of those questions the first step usually is to extract the embedded signature container and inspect it in an ASN.1 viewer.
In case of your PDF the main result of that inspection was that the signature as such looked ok and that your signed attributes don't contain any variable data.
If there had been some variable data (e.g. a signing time attribute) in them, a candidate for the cause of the issue would have been that you build the signed attributes twice, once explicitly in GetHashOfPdf
, once implicitly in EmbedSignature
, with different values for the variable data. But as mentioned above, this was not the case.
The next step here was to actually check the hashes involved. Checking the document hash is simple, one calculates the signed byte range hash and compares with the value of the MessageDigest
signed attribute, cf. the ExtractHash test testSotnSignedpdf
(in Java).
The result for your PDF turned out to be ok.
The following step was to inspect the signature container more thoroughly. In this context I once started to write some checks but did not get very far, cf. the SignatureAnalyzer class. I extended it a bit for the test of the hash of the signed attributes making use of the signature algorithm you used, the old RSASSA-PKCS1-v1_5: In contrast to many other signature algorithms, this one allows to trivially extract the signed hash.
Here the result for your PDF turned out not to be ok, the hash of the signed attributes differed from the signed hash.
There are two often seen causes for a mismatch here,
either the signed attributes are signed with a wrong encoding (it must be the regular DER encoding, not some arbitrary BER encoding and in particular not an encoding with the implicit tag the value stored in the signature has --- even larger players do this wrong sometimes, e.g. Docusign, cf. DSS-1343)
or the hash was somehow transformed during signing (e.g. the hash is base64 encoded or hashed again).
As it turned out, the latter was the case here, the hash was hashed again.
来源:https://stackoverflow.com/questions/51031446/c-sharp-pkcs7-smartcard-digital-signature-document-has-been-altered-or-corrupt