-
-
Save lordofscripts/ed595d920e9f7e227f9895762a152b98 to your computer and use it in GitHub Desktop.
.NET Binary serialization with RSA Digital Signature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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