Skip to content

Instantly share code, notes, and snippets.

@lordofscripts
Created August 5, 2017 22:57
Show Gist options
  • Save lordofscripts/ed595d920e9f7e227f9895762a152b98 to your computer and use it in GitHub Desktop.
Save lordofscripts/ed595d920e9f7e227f9895762a152b98 to your computer and use it in GitHub Desktop.
.NET Binary serialization with RSA Digital Signature
namespace SigningGist
{
[Serializable]
public class CheckTrailer
{
/// <summary>
/// HMACSHA256
/// </summary>
public byte[] Trailer1 { get; set; }
/// <summary>
/// RSA Signature
/// </summary>
public byte[] Trailer2 { get; set; }
public CheckTrailer()
{
Trailer1 = new byte[0];
Trailer2 = new byte[0];
}
}
}
using System.IO;
using System.Runtime.Serialization; // please add reference to System.Runtime.Serialization.dll
using System.Security.Cryptography;
namespace SigningGist
{
public static class ObjectDigestExtensions
{
/// <summary>
/// Gets a key based hash of the current instance.
/// </summary>
/// <typeparam name="T">
/// The type of the Cryptographic Service Provider to use.
/// </typeparam>
/// <param name="instance">
/// The instance being extended.
/// </param>
/// <param name="key">
/// The key passed into the Cryptographic Service Provider algorithm.
/// </param>
/// <returns>
/// A base 64 encoded string representation of the hash.
/// </returns>
public static byte[] GetKeyedHash<T>(this object instance, byte[] key) where T : KeyedHashAlgorithm, new()
{
T cryptoServiceProvider = new T { Key = key };
return computeHash(instance, cryptoServiceProvider);
}
private static byte[] computeHash<T>(object instance, T cryptoServiceProvider) where T : HashAlgorithm, new()
{
DataContractSerializer serializer = new DataContractSerializer(instance.GetType());
using (MemoryStream memoryStream = new MemoryStream())
{
serializer.WriteObject(memoryStream, instance);
cryptoServiceProvider.ComputeHash(memoryStream.ToArray());
return cryptoServiceProvider.Hash;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SigningGist
{
[Serializable]
public class SampleObject : ICloneable, IEquatable<SampleObject>
{
public uint HEADER { get; set; }
public SampleInfo Info { get; set; }
public CheckTrailer Check { get; set; }
public SampleObject()
{
HEADER = 0xABCDEF12;
Info = null;
Check = null;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("hdr", HEADER, typeof(uint));
info.AddValue("inf", Info, typeof(uint));
info.AddValue("trl", Check, typeof(CheckTrailer));
}
public SampleObject(SerializationInfo info, StreamingContext context)
{
Info = (SampleInfo)info.GetValue("inf", typeof(SampleInfo));
Check = (CheckTrailer)info.GetValue("trl", typeof(CheckTrailer));
}
public object Clone()
{
SampleObject so = new SampleObjet {
Info = this.Info,
Check = this.Check
};
return so;
}
}
[Serializable]
public class SampleInfo
{
public string Name { get; set; }
public string Email { get; set; }
}
}
using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
namespace SigningGist
{
public class SerializationController
{
[Flags]
public enum VerificationLevel
{
None, /// No verification done or none passed
HMAC = 1, /// Keyed Hash verification OK
DSIG = 2 /// Digital Signature OK
}
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()
public SampleObject Load(string inFilename, byte[] cspBlob, out VerificationLevel vlevel)
{
SampleObject model = null;
IFormatter formatter = new BinaryFormatter();
CheckTrailer trailers = new CheckTrailer(); // empty keyed hash & signature
vlevel = VerificationLevel.None;
using (Stream stream = new FileStream(inFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
model = (SampleObject)formatter.Deserialize(stream);
// store what we read from the file
trailers.Trailer1 = model.Check.Trailer1;
trailers.Trailer2 = model.Check.Trailer2;
model.Check = null;
if (model.GetKeyedHash<HMACSHA256>(GetKey()).SequenceEqual(trailers.Trailer1))
{
vlevel = VerificationLevel.HMAC;
}
}
if (model != null && vlevel == VerificationLevel.HMAC)
{
CspParameters cspparams = new CspParameters { Flags = CspProviderFlags.CreateEphemeralKey };
using (RSACryptoServiceProvider rsaVerify = new RSACryptoServiceProvider(cspparams))
{
rsaVerify.ImportCspBlob(cspBlob); // import the RSA Public Key from a CSP blob
model.Check = trailers;
model.Check.Trailer2 = new byte[0];
byte[] modelBlob = GetObjectBlob(model, true);
if (rsaVerify.VerifyData(modelBlob, HashAlgorithm.Create(DSIG_HASH), model.Check.Trailer2))
{
vlevel |= VerificationLevel.DSIG;
}
Console.WriteLine("\tDSIG {0} ok? {1}", BitConverter.ToString(model.Check.Trailer2), vlevel);
}
}
return model;
} // Load()
internal byte[] GetObjectBlob(SampleObject model, bool includeTrailers)
{
SampleObject shadow = model.Clone() as SampleObject;
if (!includeTrailers)
{
shadow.Check = null;
}
byte[] blob = null;
DataContractSerializer serializer = new DataContractSerializer(shadow.GetType());
using (MemoryStream memoryStream = new MemoryStream())
{
serializer.WriteObject(memoryStream, shadow);
memoryStream.Position = 0; // rewind, just in case
blob = new byte[memoryStream.Length];
int readBytes = memoryStream.Read(blob, 0, (int)memoryStream.Length); // the blob will never be as large as Int.MaxValue
if (readBytes != blob.Length)
{
throw new SerializationException("Blob from stream not as long as we expected");
}
}
return blob;
} // GetObjectBlob()
internal byte[] GetObjectBlob(string inFilename)
{
SampleObject model;
IFormatter formatter = new BinaryFormatter();
using (Stream stream = new FileStream(inFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
model = (SampleObject)formatter.Deserialize(stream);
}
byte[] blob = null;
DataContractSerializer serializer = new DataContractSerializer(model.GetType());
using (MemoryStream memoryStream = new MemoryStream())
{
serializer.WriteObject(memoryStream, model);
memoryStream.Position = 0; // rewind, just in case
blob = new byte[memoryStream.Length];
int readBytes = memoryStream.Read(blob, 0, (int)memoryStream.Length); // the blob will never be as large as Int.MaxValue
if (readBytes != blob.Length)
{
throw new SerializationException("Blob from stream not as long as we expected");
}
}
return blob;
} // GetObjectBlob()
} // class
}
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Security.Cryptography;
using System.IO;
using System.Text;
using SigningGist;
namespace SigningGist.Test
{
[TestClass]
public class TestSignature
{
[TestMethod]
public void TestSignVerify()
{
// the sample object to be hashed, signed and serialized
SampleObject model = new SampleObject {
Info = new SampleInfo { Name = "Johan Does", Email = "johan@example.com" },
Check = new CheckTrailer()
};
// the RSA Keys
CspParameters cspparams = new CspParameters { Flags = CspProviderFlags.CreateEphemeralKey };
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspparams);
string RsaXmlTestKeyPair = rsa.ToXmlString(true); // key pair
string RsaXmlTestPubKey = rsa.ToXmlString(false); // only the public key
Console.WriteLine("1. Calculating HMACSHA256...");
CheckTrailer trailers = new CheckTrailer();
trailers.Trailer1 = model.GetKeyedHash<HMACSHA256>(GetKey());
Console.WriteLine("\SampleObject HMACSHA256: {0}", BitConverter.ToString(trailers.Trailer1));
Assert.AreEqual(MY_HMACSHA256, BitConverter.ToString(trailers.Trailer1));
Console.WriteLine("2. Getting test RSA Signing Key Pair...");
SerializationController controller = new SerializationController();
using (RSACryptoServiceProvider rsaSign = new RSACryptoServiceProvider())
{
rsaSign.FromXmlString(RsaXmlTestKeyPair);
Console.WriteLine("\tImported Key Pair (PUK/PVK)");
Console.WriteLine("3. Signing & Serializing...");
controller.Save(model, rsaSign.ExportCspBlob(true) , FILENAME);
Console.WriteLine("\tSigned model saved to {0}", FILENAME);
Console.WriteLine("\tWith DSIG {0}", BitConverter.ToString( model.Check.Trailer2).Replace("-",""));
}
Console.WriteLine("4. Getting test RSA Verification Key...");
SerializationController.VerificationLevel isValid = SerializationController.VerificationLevel.None;
using (RSACryptoServiceProvider rsaVfy = new RSACryptoServiceProvider())
{
rsaVfy.FromXmlString(RsaXmlTestPubKey);
Console.WriteLine("\tImported Public key (PUK)");
Console.WriteLine("5. Deserializing & Verifying ...");
SampleObject loadedObj = controller.Load(FILENAME, rsaVfy.ExportCspBlob(false), out isValid);
Console.WriteLine("\tRetrieved Trailer: {0} valid: {1}", BitConverter.ToString(loadedLicense.Check.Trailer1), isValid);
Console.WriteLine("\tSignature Trailer: {0}", BitConverter.ToString(loadedLicense.Check.Trailer2));
}
string message;
switch (isValid)
{
case SerializationController.VerificationLevel.None:
message = "FAILED! Neither the HMAC nor the DSIG were correct";
break;
case SerializationController.VerificationLevel.HMAC:
message = "FAILED! Only the HMAC was correct, DSIG was incorrect";
break;
case SerializationController.VerificationLevel.DSIG:
message = "SUCCESS! Both HMAC & DSIG were correct";
break;
default:
message = "What?";
break;
}
Assert.IsTrue(isValid == SerializationController.VerificationLevel.DSIG, message);
Console.WriteLine("SUCCESS!: Both HMACSHA256 & RSA Digital Signature matched");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment