Mixing asymmetric and symmetric encryption, HMAC hash verification and digital signatures in .NET

Introduction

In this post we built a test application where we mixed asymmetric and symmetric encryption with HMAC hash verification. The message sender gets the asymmetric public key of the receiver and uses it to encrypt a symmetric public key. The message is encrypted with a one-time symmetric public key. The symmetric key is also used to calculate the HMAC of the cipher text. The receiver decrypts the symmetric key with her asymmetric private key and calculates the HMAC. If the hashes match then it’s safe to assume that the message hasn’t been tampered with on its way to the sender.

The communication flow is quite secure but we can tighten security even more. In this post we learnt about digital signatures. A digital signature is used to sign the hash of a message with the sender’s private key. The public key that matches the private signature key is sent along all other information to the sender. The sender can then check the validity of the signature using the provided public key. The trust is based on the fact that the public and private keys go hand in hand, therefore signature verification doesn’t need the private key.

The goal of this post is to extend the demo application with digital signatures. The sender will sign the message and the receiver will verify the validity of the signature.

Digital signature components

Let’s start with the interface for the digital signature service:

public interface IDigitalSignatureService
{
	SignMessageResult SignMessage(byte[] hashToSign, int keySizeBits, string signingHashAlgorithm);
	DigitalSignatureVerificationResult VerifySignature(SignMessageResult signMessageResult);
}

…where SignMessageResult is a container object to hold the result of the message signing process:

public class SignMessageResult : OperationResult
{
	public byte[] HashedMessage { get; set; }
	public byte[] Signature { get; set; }
	public string PublicSigningKeyXml { get; set; }
	public string SigningHashAlgorithm { get; set; }
}

OperationResult is a common base class for various response objects:

public abstract class OperationResult
{
	public bool Success { get; set; }
	public string ExceptionMessage { get; set; }
}

DigitalSignatureVerificationResult is also a simple object to hold whether the signatures match:

public class DigitalSignatureVerificationResult : OperationResult
{
	public bool SignaturesMatch { get; set; }
}

Here’s the implementation of the service:

public class RsaSigitalSignatureService : IDigitalSignatureService
{
	public SignMessageResult SignMessage(byte[] hashToSign, int keySizeBits, string signingHashAlgorithm)
	{
		SignMessageResult result = new SignMessageResult();
		try
		{
			RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(keySizeBits);
			string publicKeyXml = rsaProvider.ToXmlString(false);
			RSAPKCS1SignatureFormatter signatureFormatter =
				new RSAPKCS1SignatureFormatter(rsaProvider);
			signatureFormatter.SetHashAlgorithm(signingHashAlgorithm);
			byte[] signature = signatureFormatter.CreateSignature(hashToSign);
			result.PublicSigningKeyXml = publicKeyXml;
			result.Success = true;
			result.HashedMessage = hashToSign;
			result.Signature = signature;
			result.SigningHashAlgorithm = signingHashAlgorithm;
		}
		catch (Exception ex)
		{
			result.ExceptionMessage = ex.Message;
		}

		return result;
	}

	public DigitalSignatureVerificationResult VerifySignature(SignMessageResult signMessageResult)
	{
		DigitalSignatureVerificationResult result = new DigitalSignatureVerificationResult();
		try
		{
			RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
			rsaProvider.FromXmlString(signMessageResult.PublicSigningKeyXml);
			RSAPKCS1SignatureDeformatter signatureDeformatter = new RSAPKCS1SignatureDeformatter(rsaProvider);
			signatureDeformatter.SetHashAlgorithm(signMessageResult.SigningHashAlgorithm);
			bool signatureOk = signatureDeformatter.VerifySignature(signMessageResult.HashedMessage,
				signMessageResult.Signature);
			result.SignaturesMatch = signatureOk;
			result.Success = true;
		}
		catch (Exception ex)
		{
			result.ExceptionMessage = ex.Message;
		}
		return result;
	}
}

Up to now the code is very similar to what we saw in the post on digital signatures. If you’re not sure about the details and what the above code does then you’ll find the explanation in the referred blog post.

Extending the EncryptedMessage object

We need to extend the EncryptedMessage object of the demo application to give room for the sign message result:

public class EncryptedMessage
{
	public EncryptedMessage(string symmetricKeyEncryptedBase64, string initializationVectorBase64
		, string cipherTextBase64, Guid asymmetricKeyId, string secretMessageHmacBase64, SignMessageResult signMessageResult)
	{
		SymmetricKeyEncryptedBase64 = symmetricKeyEncryptedBase64;
		InitializationVectorBase64 = initializationVectorBase64;
		CipherTextBase64 = cipherTextBase64;
		AsymmetricKeyId = asymmetricKeyId;
		SecretMessageHmacBase64 = secretMessageHmacBase64;
		SignMessageResult = signMessageResult;
	}

	public string SymmetricKeyEncryptedBase64 { get; }
	public string InitializationVectorBase64 { get; }		
	public string CipherTextBase64 { get; }
	public Guid AsymmetricKeyId { get; }
	public string SecretMessageHmacBase64 { get; }
	public SignMessageResult SignMessageResult { get; set; }
}

The HashMac service

In the previous part we didn’t factor out the HMAC related code into an interface but this time it will be necessary for using the digital signature service later on. Here’s a simple interface:

public interface IHashMacService
{
	byte[] ComputeHashMac(byte[] bytesToHash, byte[] symmetricKey);
	string GetHashAlgorithmDescription();
}

…and here’s a simple implementation for the SHA256 based HMAC:

public class HmacSha256Service : IHashMacService
{
	public byte[] ComputeHashMac(byte[] bytesToHash, byte[] symmetricKey)
	{
		HMACSHA256 hmac = new HMACSHA256(symmetricKey);
		byte[] hmacHash = hmac.ComputeHash(bytesToHash);
		return hmacHash;
	}
	public string GetHashAlgorithmDescription()
	{
		return "SHA256";
	}
}

Message receiver and sender base

The SecretMessageReceiver and SecretMessageSender will both need to handle asymmetric and symmetric encryption, HMACs and digital signatures. These are 4 interface dependencies that both constructors will need. It’s better to factor these out to a common base class:

public abstract class SecretMessageParticipant
{
	private readonly IXmlBasedAsymmetricEncryptionService _xmlBasedAsymmetricEncryptionService;
	private readonly ISymmetricEncryptionService _symmetricEncryptionService;
	private readonly IHashMacService _hashMacService;
	private readonly IDigitalSignatureService _digitalSignatureService;

	public SecretMessageParticipant(IXmlBasedAsymmetricEncryptionService xmlBasedAsymmetricEncryptionService
		, ISymmetricEncryptionService symmetricEncryptionService, IHashMacService hashMacService,
		IDigitalSignatureService digitalSignatureService)
	{
		if (xmlBasedAsymmetricEncryptionService == null) throw new ArgumentNullException("XmlBasedAsymmetricEncryptionService");
		if (symmetricEncryptionService == null) throw new ArgumentNullException("SymmetricEncryptionService");
		if (hashMacService == null) throw new ArgumentNullException("HashMacService");
		if (digitalSignatureService == null) throw new ArgumentNullException("DigitalSignatureService");
		AsymmetricEncryptionService = xmlBasedAsymmetricEncryptionService;
		SymmetricEncryptionService = symmetricEncryptionService;
		HashMacService = hashMacService;
		DigitalSignatureService = digitalSignatureService;
	}

	public IXmlBasedAsymmetricEncryptionService AsymmetricEncryptionService { get; }		
	public ISymmetricEncryptionService SymmetricEncryptionService { get; }
	public IHashMacService HashMacService { get; }
	public IDigitalSignatureService DigitalSignatureService { get; }
}

Updated message sender and receiver

Here’s the update secret message sender class:

public class SecretMessageSender : SecretMessageParticipant
{
	private readonly SecretMessageReceiver _secretMessageReceiver;		
	private readonly string _extremelyConfidentialMessage;

	public SecretMessageSender(SecretMessageReceiver secretMessageReceiver, 
		ISymmetricEncryptionService symmetricEncryptionService, 
		IXmlBasedAsymmetricEncryptionService xmlBasedAsymmetricEncryptionService,
		IHashMacService hashMacService, IDigitalSignatureService digitalSignatureService)
		: base(xmlBasedAsymmetricEncryptionService, symmetricEncryptionService,
				hashMacService, digitalSignatureService)
	{
		if (secretMessageReceiver == null) throw new ArgumentNullException("SecretMessageReceiver");			
		_secretMessageReceiver = secretMessageReceiver;			
		_extremelyConfidentialMessage = "My new invention will save the world.";
	}

	public void Start()
	{
		int defaultSymmetricKeySize = 256;
		AsymmetricPublicKey oneTimeAsymmetricPublicKey = _secretMessageReceiver.GenerateOneTimeAsymmetricPublicKey();
		SymmetricEncryptionResult symmetricEncryptionOfSecretMessage = 
			SymmetricEncryptionService.Encrypt(_extremelyConfidentialMessage, defaultSymmetricKeySize);
		string symmetricKeyBase64 = Convert.ToBase64String(symmetricEncryptionOfSecretMessage.SymmetricKey);
		string ivBase64 = Convert.ToBase64String(symmetricEncryptionOfSecretMessage.IV);

		byte[] hmacHash = HashMacService
			.ComputeHashMac(symmetricEncryptionOfSecretMessage.Cipher, symmetricEncryptionOfSecretMessage.SymmetricKey);
		var signatureResult = DigitalSignatureService.SignMessage(hmacHash, 2048, HashMacService.GetHashAlgorithmDescription());
		if (signatureResult.Success)
		{
			string hmacHashBase64 = Convert.ToBase64String(hmacHash);

			AsymmetricEncryptionResult asymmetricallyEncryptedSymmetricKeyResult =
				AsymmetricEncryptionService.EncryptWithPublicKeyXml(symmetricKeyBase64, oneTimeAsymmetricPublicKey.PublicKeyXml.ToString());

			EncryptedMessage encryptedMessage = new EncryptedMessage(asymmetricallyEncryptedSymmetricKeyResult.EncryptedAsBase64
				, ivBase64, symmetricEncryptionOfSecretMessage.CipherBase64, oneTimeAsymmetricPublicKey.PublicKeyId, 
				hmacHashBase64, signatureResult);

			_secretMessageReceiver.ProcessIncomingMessage(encryptedMessage);
		}
		else
		{
			Console.WriteLine(signatureResult.ExceptionMessage);
		}
	}
}

We’ve made the following changes:

  • The class now derives from the SecretMessageParticipant base class
  • The HMAC is calculated via the injected IHmacService dependency
  • The sender attaches the message signature result to the set of information that she sends over to the receiver

Here comes the updated secret message receiver:

public class SecretMessageReceiver : SecretMessageParticipant
{
	private readonly Dictionary<Guid, AsymmetricKeyPairGenerationResult> _successfulKeyPairResults;

	public SecretMessageReceiver(IXmlBasedAsymmetricEncryptionService xmlBasedAsymmetricEncryptionService
		, ISymmetricEncryptionService symmetricEncryptionService, IHashMacService hashMacService, 
		IDigitalSignatureService digitalSignatureService)
		: base(xmlBasedAsymmetricEncryptionService, symmetricEncryptionService, 
				hashMacService, digitalSignatureService)
	{			
		_successfulKeyPairResults = new Dictionary<Guid, AsymmetricKeyPairGenerationResult>();
	}

	public AsymmetricPublicKey GenerateOneTimeAsymmetricPublicKey()
	{
		int defaultAsymmetricKeySize = 2048;
		AsymmetricKeyPairGenerationResult asymmKeyPairGenerationResult =
			AsymmetricEncryptionService.GenerateKeysAsXml(defaultAsymmetricKeySize);
		if (asymmKeyPairGenerationResult.Success)
		{
			Guid guid = Guid.NewGuid();
			_successfulKeyPairResults[guid] = asymmKeyPairGenerationResult;
			return new AsymmetricPublicKey(guid, XDocument.Parse(asymmKeyPairGenerationResult.PublicKeyXml));
		}
		throw new CryptographicException(asymmKeyPairGenerationResult.ExceptionMessage);
	}

	public void ProcessIncomingMessage(EncryptedMessage encryptedMessage)
	{
		if (_successfulKeyPairResults.ContainsKey(encryptedMessage.AsymmetricKeyId))
		{
			byte[] encryptedSymmetricKey = Convert.FromBase64String(encryptedMessage.SymmetricKeyEncryptedBase64);
			AsymmetricDecryptionResult decryptSymmetricKey = AsymmetricEncryptionService.DecryptWithFullKeyXml
				(encryptedSymmetricKey,
				_successfulKeyPairResults[encryptedMessage.AsymmetricKeyId].PublicPrivateKeyPairXml);

			if (decryptSymmetricKey.Success)
			{
				string symmetricKeyBase64 = decryptSymmetricKey.DecryptedMessage;
				byte[] cipherText = Convert.FromBase64String(encryptedMessage.CipherTextBase64);

				byte[] hmacInMessage = Convert.FromBase64String(encryptedMessage.SecretMessageHmacBase64);
				byte[] hmacToCheckAgainst = HashMacService.ComputeHashMac(cipherText, Convert.FromBase64String(decryptSymmetricKey.DecryptedMessage));					
				bool hmacsEqual = hmacInMessage.SequenceEqual(hmacToCheckAgainst);
				if (!hmacsEqual)
				{
					throw new CryptographicException("Message hash mismatch!!!!!");
				}

				var signatureVerificationResult = DigitalSignatureService.VerifySignature(encryptedMessage.SignMessageResult);
				if (!signatureVerificationResult.SignaturesMatch)
				{
					throw new CryptographicException("Signature mismatch!!!");
				}
				byte[] iv = Convert.FromBase64String(encryptedMessage.InitializationVectorBase64);
				byte[] symmetricKey = Convert.FromBase64String(symmetricKeyBase64);
				string secretMessage = SymmetricEncryptionService.Decrypt(cipherText
					, symmetricKey, iv);
				Console.WriteLine($"Secret message receiver got the following message: {secretMessage}");
				_successfulKeyPairResults.Remove(encryptedMessage.AsymmetricKeyId);
			}
			else
			{
				throw new CryptographicException(decryptSymmetricKey.ExceptionMessage);
			}
		}
		else
		{
			throw new ArgumentException("No such key id found");
		}
	}
}

We’ve added an extra step to the message receiver class. It will verify the signature of the message. If there’s a mismatch then an exception is thrown. Otherwise code execution continues like before and the encrypted message is decrypted.

Putting it all together

Here’s an example how the above components can be put together to test the flow:

private static void TestHybridEncryption()
{
	IXmlBasedAsymmetricEncryptionService asymmetricEncryptionService = new RsaXmlBasedAsymmetricEncryptionService();
	ISymmetricEncryptionService symmetricEncryptionService = new SymmetricEncryptionService(new AesCryptoServiceProvider());
	IHashMacService hashMacService = new HmacSha256Service();
	IDigitalSignatureService digitalSignatureService = new RsaSigitalSignatureService();
	SecretMessageReceiver secretMessageReceiver = new SecretMessageReceiver(asymmetricEncryptionService, 
		symmetricEncryptionService, hashMacService, digitalSignatureService);
	SecretMessageSender secretMessageSender = 
		new SecretMessageSender(secretMessageReceiver, symmetricEncryptionService, asymmetricEncryptionService, 
		hashMacService, digitalSignatureService);
	secretMessageSender.Start();
}

If everything goes well then you should only see a single line in the command window:

Secret message receiver got the following message: My new invention will save the world.

We’re done with our little demo application. We now have a small messaging flow where the message is secured in the following ways:

  • It is encrypted with a symmetric encryption algorithm
  • The symmetric key is encrypted with an asymmetric encryption algorithm which is very difficult to break
  • The message hash is used to verify that it hasn’t been altered along the way
  • The message hash is also signed by the sender so that the receiver knows that it’s really coming from the right source

You can view the list of posts on Security and Cryptography here.

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.