Search code examples
c#.netcryptographybouncycastlepkcs#7

Verify PKCS7 (CMS) Signature with only a hash of the original data


I am trying to verify a detached pkcs7 CMS signature with System.Security.Cryptography or the bouncycastle library and still cannot get it to work after hours and hours of testing.

My problem is that I only receive a hash of the original data to sign along with the detached cms signature of that hash from a external source and I need to verify the signature.

I got the following code to work and verify the signature, when instead of hashedData I input the original data. However I do not have access to that and only receive the hash of that data.

I suppose it is not working because the CheckSignature method hashes the hashedData internally again before validating the signature and I need a way to suppress that behavior.

Is there any way to validate the signature without having the original data?

byte[] originalData = Encoding.UTF8.GetBytes("Test");
byte[] hashedData = new SHA512Managed().ComputeHash(data); // hashedData is what I receive along with the signature

var content = new System.Security.Cryptography.Pkcs.ContentInfo(originalData); // working, but I dont have original data
var content = new System.Security.Cryptography.Pkcs.ContentInfo(hashedData); // not working

var cms = new System.Security.Cryptography.Pkcs.SignedCms(content, true);
cms.Decode(signature);
cms.CheckSignature(true);

I also tried to specify that the data is already hashed in the ContentInfo constructor, but that didn't work either.

If this is not possible in .NET, I am also welcoming a solution with BouncyCastle. See below for my current code in bouncycastle, which also only works with the original data, but not its hash:

var certificate = DotNetUtilities.FromX509Certificate(workerCert);
var processable = new CmsProcessableByteArray(hashedData);

var cms = new CmsSignedData(processable, signature);
var signers = cms.GetSignerInfos();
var signersCollection = signers.GetSigners();

foreach (var signer in signersCollection.Cast<SignerInformation>())
{
    if (signer.Verify(certificate.GetPublicKey()))
    {
        Console.WriteLine("Working");
    }
}

Solution

  • With BouncyCastle this is possible using:

    CmsSignedData.CmsSignedData(System.Collections.IDictionary hashes, byte[] sigBlock)
    

    In the absence of a documentation for C#/BouncyCastle, here is the link to the documentation of the Java/BouncyCastle counterpart.

    Below is a sample implementation based on the code you posted:

    // get public key for verification 
    var certDer = Convert.FromBase64String("MIICojCCAYqgAwIBAgIEb9dpqTANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhCYWVsZHVuZzAeFw0yNDAyMDcwOTU3MTRaFw0yNTAyMDYwOTU3MTRaMBMxETAPBgNVBAMTCEJhZWxkdW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnpWtJNYIsuYn2h//qFag6aQFzw5gYoZhksH0iYroOnh23ALOzGmHodTI2tikaMnunmPw70HSt5ey36N8eb/gxWmeUFtyfvOF7VQCkuXa15BwtikvTu8L2tEFEobCm+P8YMNmbpz9nYlSVT7Kvk/DMZyU++P7O/sZQ8ui9F8KTFQKBlYTkQSgb5l9yYEiqt/3px8hyY4xVmuZWYSj9d8MKhMDq0ubXgd+RHIuGUVQWogSAUpP2WKJUJTU+seX/H1pNJJ6wCxVx+NHjjZ21rMWFBn1mQ1uZOpUdWkl+bmyJTkTt2v2yW3ZAZZ2EQIIgTkAu8/t92Z9BYpU5Co/y847UQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQCPCMZwR+AJEzhV6pSR2IqeimTsk2qFFDV+No7ssnZKVcTz59p6F/z6lkHDHS/88KrOuxBgfyaJMJqMFd3UqouyyGXTtIzCk9NIhM9IBNOkjejAPxT5Vw4Kdcag6yRYVoge/l+KlePaJP3cMtvxUK42EBFn8EtYnqpvbndAFB40ukIHZHqermB0I2W/WRkHMN5gHxOsquLCKbWzqLm6HvhyORIFQNmM2yMIIVQ51CsxWuruKbn2CNSac/Cf6Q00NTMfOevyjLNFCJIWSkQAn9ZBf03TC/jESaR/Fp2Bss66bC1FvH/tYFkI0KyHSJAXDaFaakWR2OKucxNm0nBHcYb7");
    var cert = new X509Certificate(certDer);
    var certificate = DotNetUtilities.FromX509Certificate(cert);
    var publicKey = certificate.GetPublicKey();
    
    // derive hash from originalData (just to prove that the hash is indeed the hash of originalData)
    var originalData = Encoding.UTF8.GetBytes("Hello");
    var hashes = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
    hashes.Add(CmsSignedGenerator.DigestSha512, DigestUtilities.CalculateDigest("SHA512", originalData));
    
    // signature
    var signature = Convert.FromBase64String("MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgMwCwYJKoZIhvcNAQcBoIAwggKiMIIBiqADAgECAgRv12mpMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNVBAMTCEJhZWxkdW5nMB4XDTI0MDIwNzA5NTcxNFoXDTI1MDIwNjA5NTcxNFowEzERMA8GA1UEAxMIQmFlbGR1bmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCela0k1giy5ifaH/+oVqDppAXPDmBihmGSwfSJiug6eHbcAs7MaYeh1Mja2KRoye6eY/DvQdK3l7Lfo3x5v+DFaZ5QW3J+84XtVAKS5drXkHC2KS9O7wva0QUShsKb4/xgw2ZunP2diVJVPsq+T8MxnJT74/s7+xlDy6L0XwpMVAoGVhORBKBvmX3JgSKq3/enHyHJjjFWa5lZhKP13wwqEwOrS5teB35Eci4ZRVBaiBIBSk/ZYolQlNT6x5f8fWk0knrALFXH40eONnbWsxYUGfWZDW5k6lR1aSX5ubIlORO3a/bJbdkBlnYRAgiBOQC7z+33Zn0FilTkKj/LzjtRAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAI8IxnBH4AkTOFXqlJHYip6KZOyTaoUUNX42juyydkpVxPPn2noX/PqWQcMdL/zwqs67EGB/JokwmowV3dSqi7LIZdO0jMKT00iEz0gE06SN6MA/FPlXDgp1xqDrJFhWiB7+X4qV49ok/dwy2/FQrjYQEWfwS1ieqm9ud0AUHjS6Qgdkep6uYHQjZb9ZGQcw3mAfE6yq4sIptbOouboe+HI5EgVA2YzbIwghVDnUKzFa6u4pufYI1Jpz8J/pDTQ1Mx856/KMs0UIkhZKRACf1kF/TdML+MRJpH8WnYGyzrpsLUW8f+1gWQjQrIdIkBcNoVpqRZHY4q5zE2bScEdxhvsAADGCAf0wggH5AgEBMBswEzERMA8GA1UEAxMIQmFlbGR1bmcCBG/XaakwCwYJYIZIAWUDBAIDoIG2MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTI0MDIwNzA5NTcxNFowKwYJKoZIhvcNAQk0MR4wHDALBglghkgBZQMEAgOhDQYJKoZIhvcNAQENBQAwTwYJKoZIhvcNAQkEMUIEQDYV+AydKT7XQCaH+Usi1Y5Sm4zHkW+PrH/d9/vVr0z3d9PXlaegCha/fn8/uVYe6bquSA2p/noYdp5xiGsD8xUwDQYJKoZIhvcNAQENBQAEggEAgf0GVqkoCFDjndkW8L4tsU38vAxn0I3hr8CEgg0QTgPUGib47FJFAjCLT6DaRc6lclA1YQKUnFJipc6MSFGe4SgbIw7bIwID0ZZYel4825jGw4h92+VbXaqE2IT5s9jv7BYUAdWYE4mI7AqJMi3cJJK/x/C8mDwNmeUloQ857kdUqgpZet0D0mWBAJ7ezEFoCQkCq3znSxuNTP57J8zTL8zoIOHkH+wskWwKyjnuGsf+Q0M8R6NYYtDwmRKGuK9Ao+wTawb1QDDxrvuKXNw3bcpQDuMTHJrKz9785M5pLZSiyL30ASoDTHn2FUqQDg+7Fm1wngwvmXpnDQctQY64QQAAAAAAAA==");
    
    // verify
    var cms = new CmsSignedData(hashes, signature);
    var signers = cms.GetSignerInfos();
    var signersCollection = signers.GetSigners();
    foreach (var signer in signersCollection.Cast<SignerInformation>())
    {
        if (signer.Verify(publicKey))
        {
            Console.WriteLine("Working");
        }
    }