Search code examples
.netcryptographydatacontractserializersigningbinary-serialization

Cannot get my RSA signature/verifiction on BINARY data to work. Any working examples?


I am trying to use RSA signature and verification on binary data but it is just not working, the RSA.VerifyData always returns false no matter what.

Basically I have an object that is binary serialized:

public class MyObject {
    public OtherClass Property1 { get; set; }

    public Trailer Trailer { get; set; }
}

public class Trailer {
    public byte[] Hash {get;set; } // Any hash
    public byte[] Signature {get;set; }
}

Basically the object has its own properties set, here just one (Property1). And then I compute the hash over the object without the trailer and put it on Trailer.Hash. Then I compute the RSA signature over Property1, Trailer.Hash and Trailer.Signature = null. The Binary serialization I am doing with DataContractSerializer.

The problem is when I retrieve the hash is good and it also regenerates well (recomputed on load) but the signature verification fails. Tried many approaches but nothing seems to work.

Can anybody point me to a WORKING example of what I want to do? I can sign a string and verify it but with binary data I cannot get it to work. For a more detailed code see my Gist on Github/

public void Save(SampleObject model, byte[] cspBlob, string outFilename)
{
    // Calculate the HMACSHA256 over a model with null Trailer section
    CheckTrailer check = new CheckTrailer();    // empty keyed hash and empty signature
    model.Check = null; // both HMAC & Signature are irrelevant for Save because we calculate them here
    check.Trailer1 = model.GetKeyedHash<HMACSHA256>(GetKey());

    DataContractSerializer serializer = new DataContractSerializer(model.GetType());
    using (MemoryStream memoryStream = new MemoryStream())
    {
        model.Check = check;
        // serialize with keyed hash and empty signature
        serializer.WriteObject(memoryStream, model);

        CspParameters cspparams = new CspParameters { Flags = CspProviderFlags.CreateEphemeralKey };
        using (RSACryptoServiceProvider rsaSign = new RSACryptoServiceProvider(cspparams))
        {
            rsaSign.ImportCspBlob(cspBlob);     // must be a key pair blob
            if (rsaSign.PublicOnly)
            {
                throw new CryptographicException("Cannot sign with PUK");
            }

            check.Trailer2 = rsaSign.SignData(memoryStream, HashAlgorithm.Create(DSIG_HASH));
            Console.WriteLine("\tDSIG: {0}", BitConverter.ToString(check.Trailer2));
        }
    }

    // Place the computed signature
    model.Check.Trailer2 = check.Trailer2;

    // serialize
    IFormatter formatter = new BinaryFormatter();
    using (Stream stream = new FileStream(outFilename, FileMode.Create, FileAccess.Write, FileShare.None))
    {
        formatter.Serialize(stream, model);
    }
} // Save()

Solution

  • Can anybody point me to a WORKING example of what I want to do? I can sign a string and verify it but with binary data I cannot get it to work.

    RSA is only defined over binary data, so the problem is that you're not producing the same binary data to sign and verify.

    Based on your Save method, the problem is that you are signing the empty stream, but verifying a non-empty stream.

    // New stream.  Position=0, Length=0
    using (MemoryStream memoryStream = new MemoryStream())
    {
        // Write some data.  After this, Position=X, Length=X
        serializer.WriteObject(memoryStream, model);
    
        ...
    
        // RSA.SignData will read from Position until Length in the stream.
        // Since Position=Length already, this means it's the same as calling
        // SignData(Array.Empty<byte>)
        check.Trailer2 = rsaSign.SignData(memoryStream, HashAlgorithm.Create(DSIG_HASH));
    

    You need to reset the memory stream's position to 0 before calling SignData:

    memoryStream.Position = 0;
    check.Trailer2 = rsaSign.SignData(memoryStream, ...)