C# Verifying PDF signature - c#

Trying to validate PDF signature isn't working. The PDF were signed by Adobe Acrobat and then trying to verify it with the public key of the client certificate.
So I get the public key of the client certificate, hash the PDF and verify if the hash is equal to the pdf signature, but it fails.
HttpClientCertificate cert = request.ClientCertificate;
X509Certificate2 cert2 = new X509Certificate2(cert.Certificate);
PdfReader pdfreader = new PdfReader("path_to_file");
AcroFields fields = pdfreader.AcroFields;
AcroFields.Item item = fields.GetFieldItem("Signature1");
List<string> names = fields.GetSignatureNames();
foreach (string name in names){
PdfDictionary dict = fields.GetSignatureDictionary(name);
PdfPKCS7 pkcs7 = fields.VerifySignature(name);
Org.BouncyCastle.X509.X509Certificate pdfSign = pkcs7.SigningCertificate;
// Get its associated CSP and public key
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert2.PublicKey.Key;
// Hash the data
SHA256 sha256 = new SHA256Managed();
byte[] pdfBytes = System.IO.File.ReadAllBytes("path_to_pdf");
byte[] hash = sha256.ComputeHash(pdfBytes);
// Verify the signature with the hash
bool ok = csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), pdfSing.GetSignature());
}

First, to verify whether the signature correctly you can simply use the PdfPKCS7 object you already retrieved, more exactly its Verify method:
/**
* Verify the digest.
* #throws SignatureException on error
* #return <CODE>true</CODE> if the signature checks out, <CODE>false</CODE> otherwise
*/
virtual public bool Verify()
Thus, you can simply call
bool ok = pkcs7.Verify();
and ok is true only if the document hash matches the hash in the signature.
Concerning your attempt to calculate the document hash like this
byte[] pdfBytes = System.IO.File.ReadAllBytes("path_to_pdf");
byte[] hash = sha256.ComputeHash(pdfBytes);
This indeed gives you the hash value of the complete PDF.
For document types with integrated signatures like PDFs, though, this is not the hash of interest because the complete PDF obviously includes the integrated signature!
Thus, you have to find the space reserved for the signature in the PDF and ignore it during hash calculation, cf. this answer on Information Security Stack Exchange, in particular this image:
In case of multiple signatures you furthermore have to consider that the earlier signatures only sign a former revision of the PDF, so the hash is to be calculated only for a starting segment of the file, cf. this image from the answer referenced above:
The iText(Sharp) method PdfPKCS7.Verify() takes all this into account.

Related

C# PKCS7 Smartcard Digital Signature - Document has been altered or corrupted since it was signed

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==
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 is
1DC7CAA50D88243327A9D928D5FB4F1A61CBEFF9E947D393DDA705BD61B67F25
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.

Signature is Invalid for PDF File with iText

I am using swisscom digital signature service and we have a test account. Well the service requires the hash code the pdf file . We send it with
DIGEST_VALUE=$(openssl dgst -binary -SHA256 $FILE | openssl enc -base64 -A)
and I get a PKCS#7 response. You can decode the my signature response by using this website https://certlogik.com/decoder/
and the signature content is http://not_need_anymore
I have the same problem as follow (because we use the same code)
Digital Signature (PKCS#7 - Deferred Signing) / The document has been altered or corrupted since the signature was applied
my response has been with sha256 crypted.
Well I am using iText with c# to sign the pdf file. I sign and I see some details (such as reason, location etc).
here is the method that creates a pdf file with a signature field
public static string GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName)
{
if (File.Exists(tempPdf))
File.Delete(tempPdf);
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.SetVisibleSignature(new Rectangle(36, 748, 250, 400), 1, signatureFieldName);
//IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
PdfSignature external2 = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);//ADBE_PKCS7_SHA1);
//as pdf name I tried also PdfName.ETSI_RFC3161
//(ref => https://github.com/SCS-CBU-CED-IAM/itext-ais/blob/master/src/com/swisscom/ais/itext/PDF.java)
appearance.Reason = "For archive";
appearance.Location = "my loc";
appearance.SignDate = DateTime.Now;
appearance.Contact = "myemail#domain.ch";
appearance.CryptoDictionary = external2;
var level = reader.GetCertificationLevel();
// check: at most one certification per pdf is allowed
if (level != PdfSignatureAppearance.NOT_CERTIFIED)
throw new Exception("Could not apply -certlevel option. At most one certification per pdf is allowed, but source pdf contained already a certification.");
appearance.CertificationLevel = level;
MakeSignature.SignExternalContainer(appearance, external,30000);
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
return Convert.ToBase64String(array);
}
}
}
Actualls I do not use what this method returns. Because it already creates a temp pdf file with signature field.
After that,I give the hash code of this pdf file and get PKCS#7 responde. and then using the following function, I am adding the signature to a pdf (it creates another pdf file).
public static void EmbedSignature(string tempPdf, string signedPdf,
string signatureFieldName, string signature)
{
byte[] signedBytes = Convert.FromBase64String(signature);
using (PdfReader reader = new PdfReader(tempPdf))
{
using (FileStream os = File.OpenWrite(signedPdf))
{
IExternalSignatureContainer external =
new MyExternalSignatureContainer(signedBytes);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external);
}
}
}
the signature parameter in the method, I give p7s file content as follows
string signatureContent = File.ReadAllText(#"mypath\signed_cert.p7s");
signatureContent = signatureContent
.Replace("-----BEGIN PKCS7-----\n", "")
.Replace("-----END PKCS7-----\n","").Trim();
what am I missing or doing wrong?
In contrast to the regular detached signatures which sign the whole signed file, integrated signatures in PDFs sign (and can only sign) everything but the space set aside for the signature itself.
(For more backgrounds read this answer)
Thus, when you after preparing the PDF with a placeholder to embed the signature
give the hash code of this pdf file and get PKCS#7 responde
you hash too much because your hashing included the (then empty, i.e. filled with '0' characters) placeholder for the actual signature. The method GetBytesToSign instead only returns the hash over the signed byte ranges, i.e. everything but the placeholder:
byte[] array = SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
You have to either takes this value or similarly hash only everything but the placeholder for the signature.

Verify electronic signature in .NET

I have a set of questions:
What does a .p7s file contain? I read that it usually is an encrypted message sent to an e-mail, but in my case I have a file and a .p7s file on my hdd. The file is much larger than the .p7s, so I am wondering what is in it (and of course how to later use it).
2.this question occurs mostly because I have no naswer for 1. - If I have my public key in the form of a byte array, how can I verify the signature in C#? I found this on the internet, but again, it uses some text gotten from an e-mail and I honestly don't know what is happening:
public static bool Verify(byte[] signature, string text)
{
X509Certificate2 certificate = new X509Certificate2(#"D:\My-CV.docx.p7s");
if (signature == null)
throw new ArgumentNullException("signature");
if (certificate == null)
throw new ArgumentNullException("certificate");
//hash the text
// Methode 3 for Hashing
System.Security.Cryptography.SHA1 hash3 = System.Security.Cryptography.SHA1.Create();
System.Text.UnicodeEncoding encoder = new System.Text.UnicodeEncoding();
byte[] combined = encoder.GetBytes(text);
byte[] hash3byte = hash3.ComputeHash(combined);
//Adding the text from the email, to a contentInfo
ContentInfo content = new ContentInfo(hash3byte);
// decode the signature
SignedCms verifyCms = new SignedCms(content, true);
verifyCms.Decode(signature);
// verify it
try
{
verifyCms.CheckSignature(new X509Certificate2Collection(certificate), false);
return true;
}
catch (CryptographicException)
{
return false;
}
}
Can someone help me with this?
The .p7s file is containing the signature of your document. The content is not encrypted, but it's look like.
The document file is much larger, because it contains the date, the .p7s the signature.
To check the signature, you need the public part of the certificate with which the document was signed.
You can check this two posts to check the signature :
Check signature for x509 certificate
In C#, sign an xml with a x.509 certificate and check the signature
The last thing is to load the signature data, from the p7s file.
I make some search, and come back to you.

Digital Signature Verification using BouncyCastle - ECDSA with SHA 256, C#

Following is my scenario
- I read data from a barcode and its converted into a plain text. This text is a combination of barcode data + digital signature. Digital signature is appended to the end, which enables me to separate out the actual data and digital signature data. Digital signature data is hashed using sha256
-User send me a public key as windows certificate file ( .cer extension).
Required implementation :
-Need to extract public key from the certificate and validate the public key against the barcode data and the digital signature provided.
Here is the code I am trying to use to verify signature
//Note :
//1. certPath : is the path where my certificate is located
//2. GetStreamdata get the stream of data from the certificate.
//Get the certificate data here
Org.BouncyCastle.X509.X509Certificate cert1 = new X509CertificateParser().ReadCertificate(GetStreamData(cerPath));
//get the public key
ECPublicKeyParameters ecPublic = (ECPublicKeyParameters)cert1.GetPublicKey();
//create a signerutility with type SHA-256withECDSA
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
//initial signer with the public key
signer.Init(false, ecPublic);
//get signature in bytes : digitalsignature parameter contains signature that should be used.
byte[] dBytes = encoding.GetBytes(digitalsignature);
//block/finalise update to signer : data : is the actual data.
signer.BlockUpdate(data, 0, data.Length);
try
{
//verify signature
verified = signer.VerifySignature(dBytes);
}
catch(Exception ex)
{
_log.LogException(ex);
}
what was I able to achieve was : extract public using bouncy castle libraries
Problem :
Exception thrown on signer.verifysignature
Message=Unable to cast object of type 'Org.BouncyCastle.Asn1.DerApplicationSpecific' to type 'Org.BouncyCastle.Asn1.Asn1Sequence'.
Source=BouncyCastle.Crypto
The problem was that I had to encode digitalsignature value in iso-8859-1. I was encoding in ASCII before.
This solves the problem and I was able to validate signature.

How to use c# to decrypt an MD5 hash that is encrypted using RSA1024

I trying to verify the integrity of a file at work and an having a hard time of it. I'm not very well versed with encryption and hashing, so bear with me.
I have some files that have an MD5 hash located at the end of them. I have written code to grab the bytes that I think are the hash and they seen to be uniformly 128 bytes long. In the file, just before the hash, is the keyword "RSA1024", which I have taken to mean the hash is encrypted using RSA 1024.
I have what I know is the RSA key in a file, and have read out the bytes (always 258 bytes long). I have seen many tutorials which use FromXmlString() to pull in the key, but this RSA key was not generated using the .net framework, and is not in an XML format.
I have written the following method to decrypt the hash data using the key, and it throws this error when executing ImportCspBlob() - System.Security.Cryptography.CryptographicException: Bad Version of provider.
Any ideas?
public byte[] DecryptRSA(byte[] encryptedData, byte[] keyData)
{
CspParameters param = new CspParameters();
param.Flags = CspProviderFlags.UseExistingKey;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(param);
rsaProvider.ImportCspBlob(keyData);
byte[] decryptedData = rsaProvider.Decrypt(encryptedData, false);
return decryptedData;
}
Basic Algorithm
It may sound strange to want to "decrypt an MD5 hash", and especially when one says that they want to "decrypt it with a public key". But that is how digital signatures work. With RSA you can:
encrypt with private key
decrypt with the public key
The message digest is encrypted with the private key, and can then only be decrypted with the public key. That way you know that only the person with the private key could have signed the message.
Your key is most likely not a CSP-type key (it is most likely DER encoded). You can decrypt it using Bouncy Castle with the DER key like this:
RsaPrivateCrtKeyParameters privateKey = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(key);
byte[] rv = null;
RsaEngine eng = new RsaEngine();
eng.Init(false, privateKey);
int size = eng.GetOutputBlockSize();
rv = eng.ProcessBlock(cipher, 0, cipher.Length);
EDIT: to addressing GregS scenario that it may be a signature verify operation
If you are trying to verify a signature, you would need a certificate used to verify a message, the original message text, and the existing message signature to compare against.
What you do is pass in the original message text (minus the signature), the bytes of the message signature, and the path to the certificate you will use to verify the passed in signature.
Then, you will hash the original message and compare the result against the passed in signature.
Here is some code to illustrate:
private bool VerifySignature(string messageText, byte[] messageSignature, string certificatePath)
{
// Load the certificate from a file
X509Certificate2 cert = new X509Certificate2(certificatePath);
// Get public key
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)cert.PublicKey.Key;
// Next, hash the messageText
SHA1Managed sha1 = new SHA1Managed();
byte[] messageBytes = Encoding.Unicode.GetBytes(messageText);
byte[] hash = sha1.ComputeHash(messageBytes);
// Verify the signature with the hash
return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), messageSignature);
}
MD5 is one-way hash. But you might check out hashing algorithm. There are some ways to break this hash, just do some research ;)

Categories

Resources