Thursday, 28 December 2023

Digital signatures with RSA in .NET

I have looked at Digital signatures with RSA in .NET today. Digital signatures are used to provide non-repudiation, an authenticity proof that the original sender is who the sender claims to be and also that the data has not been hampered with. We will return a tuple of both a SHA-256 computed hash of some document data and also its digital signature using the RSA algorithm. I have used .netstandard 2.0 here, so the code can be used in most frameworks in both .NET Framework and .NET. We will use RSA here to do the digital signature signing and verification. First off, here is a helper class to create a RSA encrypted signature of a SHA-256 hash, here we create a new RSA with key size 2048. RsaDigitalSignature.cs
 
 
 public class RsaDigitalSignature
{
	private RSA _rsa;


	public RsaDigitalSignature()
	{
		_rsa = RSA.Create();
		_rsa.KeySize = 2048;
	}
	
	public static byte[] ComputeHashSha256(byte[] toBeHashed)
	{
		using (var sha256 = SHA256.Create())
		{
			return sha256.ComputeHash(toBeHashed);
		}
	}

	public (byte[] Signature, byte[] HashOfData) SignData(byte[] dataToSign)
	{
		var hashOfDataToSign = ComputeHashSha256(dataToSign);
		return (_rsa.SignHash(
			hashOfDataToSign,
			HashAlgorithmName.SHA256,
			RSASignaturePadding.Pkcs1),
			hashOfDataToSign);
	}

	public bool VerifySignature(byte[] signature, byte[] hashOfDataToSign)
	{
		return _rsa.VerifyHash(hashOfDataToSign, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
	}

}
 
 
 
In the code above, we receive some document data and create the SHA-255 hash, which is computed. We return a tuple with the signed hash from the computed SHA-256 hash and also the computed SHA-256 hash itself. A console application that runs the sample code above is the following:
 
 
 void Main()
{
	SignAndVerifyData();
	//Console.ReadLine();
}

private static void SignAndVerifyData()
{
	Console.WriteLine("RSA-based sigital signature demo");
	var document = Encoding.UTF8.GetBytes("Document to sign");	
	var digitalSignature = new RsaDigitalSignature();
	var signature = digitalSignature.SignData(document);
	bool isValidSignature = digitalSignature.VerifySignature(signature.Signature, signature.HashOfData);
	Console.WriteLine($"\nInput Document:\n{Convert.ToBase64String(document)}\nIs the digital signature valid? {isValidSignature} \nSignature: {Convert.ToBase64String(signature.Signature)} \nHash of data:\n{ Convert.ToBase64String(signature.HashOfData)}");
}
 
 
Our verification of the signature shows that the verification of the digital signature passes.
 
Input Document:
RG9jdW1lbnQgdG8gc2lnbg==
Is the digital signature valid? True
Signature: Gok1x8Wxm9u5jTRcqrgPsI45ie3WPZLi/FNbaJMGTHqBmNbpJTEYjsXix97aIF6uPjgrxQWJKCegc8S4yASdut7TpJafO9wSRqvScc2SuOGK9BqnX+9GwRRQNti8ynm0ARRar+Z4hTpYY/XngFZ+ovvqIT3KRMK/7tsMmTg87mY0KelteFX7z7G7wPB9kKjT6ORYK4lVr35fihrbxei0XQP59YuEdALy+vbvKUm3JNv4sBU0lc9ZKpp2XF0rud8UnY1Nz4/XH7ZoaKfca5HXs9yq89DJRaPBRi1+Wv41vTCf8zFKPWZJrw6rm6kBMNHMENYbeBNdZyiCspTsHZmsVA==
Hash of data:
VPPxOVW2A38lCB810vuZbBH50KQaPSCouN0+tOpYDYs=
 
The code above uses a RSA created on the fly and is not so easy to share between a sender and a receiver. Let's look at how we can use X509 certificates to do the RSA encyption. It should be possible to share the source code below between the sender and the receiver and for example
export the public part of the X509 certificate to the receiver, which the receiver could install in a certificate store, only requred to know the thumbprint of the cert which is easy to see in MMC (Microsoft Management Console) or using Powershell and cd-ing into cert:\ folder . Let's first look at a helper class to get hold of a installed X509 certificate.



public class CertStoreUtil
{
	public static System.Security.Cryptography.X509Certificates.X509Certificate2 GetCertificateFromStore(
	System.Security.Cryptography.X509Certificates.StoreLocation storeLocation,
	string thumbprint, bool validOnly = true) {
	 var store = new X509Store(storeLocation);
	 store.Open(OpenFlags.ReadOnly);
	 var cert = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly).FirstOrDefault();
	 store.Close();
	 return cert;
	}
}



Next up, a helper class to create a RSA-based digital signature like in the previous example, but using a certificate.

 
 
 public class RsaFromCertDigitalSignature
{
	private RSA _privateKey;
	private RSA _publicKey;

	public RsaFromCertDigitalSignature(StoreLocation storeLocation, string thumbprint)
	{
		_privateKey = CertStoreUtil.GetCertificateFromStore(StoreLocation.LocalMachine, thumbprint).GetRSAPrivateKey();
		_publicKey = CertStoreUtil.GetCertificateFromStore(StoreLocation.LocalMachine, thumbprint).GetRSAPrivateKey();
	}

	public static byte[] ComputeHashSha256(byte[] toBeHashed)
	{
		using (var sha256 = SHA256.Create())
		{
			return sha256.ComputeHash(toBeHashed);
		}
	}

	public (byte[] Signature, byte[] HashOfData) SignData(byte[] dataToSign)
	{
		var hashOfDataToSign = ComputeHashSha256(dataToSign);
		return (_privateKey.SignHash(
			hashOfDataToSign,
			HashAlgorithmName.SHA256,
			RSASignaturePadding.Pkcs1),
			hashOfDataToSign);
	}

	public bool VerifySignature(byte[] signature, byte[] hashOfDataToSign)
	{
		return _publicKey.VerifyHash(hashOfDataToSign, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
	}

}

 
 
A console app that tests out the code above is shown next, I have selected a random cert on my dev pc here.

 
 
 void Main()
{
	SignAndVerifyData();
	//Console.ReadLine();
}

private static void SignAndVerifyData()
{
	Console.WriteLine("RSA-based sigital signature demo");
	var document = Encoding.UTF8.GetBytes("Document to sign");

	//var x509CertLocalHost = CertStoreUtil.GetCertificateFromStore(StoreLocation.LocalMachine, "1f0b749ff936abddad89f4bbea7c30ed64e3dd07");
		
	var digitalSignatureWithCert = new RsaFromCertDigitalSignature(StoreLocation.LocalMachine, "1f0b749ff936abddad89f4bbea7c30ed64e3dd07");
	var signatureWithCert = digitalSignatureWithCert.SignData(document);
	bool isValidSignatureFromCert = digitalSignatureWithCert.VerifySignature(signatureWithCert.Signature, signatureWithCert.HashOfData);
    Console.WriteLine(
		$@"Input Document:
		{Convert.ToBase64String(document)}
		Is the digital signature signed with private key of CERT valid according to public key of CERT? {isValidSignatureFromCert}
		Signature: {Convert.ToBase64String(signatureWithCert.Signature)} 
		Hash of data:\n{Convert.ToBase64String(signatureWithCert.HashOfData)}");
}

 
 
Now here is an important concept in digital signatures :
  • For digital signatures, we MUST use a private key (e.g. private key of RSA instance, which can either be made on the fly or retrieved from for example a X509 certificate. Or a Json web key in a more modern example.
  • For digital signature, to verify a signature we can use either the public or the private key, usually just the public key (which can be shared). For X509 certiifcates, we usually share a public cert (.cert or similar format) and keep our private cert ourselves (.pfx).
Sample output of the console app shown above:
 
 RSA-based sigital signature demo
Input Document:
    RG9jdW1lbnQgdG8gc2lnbg==
    Is the digital signature signed with private key of CERT valid according to public key of CERT? True
    Signature: ZHWzJeZnwbfI109uK0T4ubq4B+CHedQPIDgPREz+Eq9BR6A9y6kQEvSrxqUHvOppSDN5kDt5bTiWv1pvDPow+czb7N6kmFf1zQUxUs3ip4WPovBtQKmfpf9/i3DNkRILcoMLdZdKnn0aSaK66f0oxkSIc4nEkb3O9PbejVso6wLqSdDCh96d71gbHqOjyiZLBj2VlqalWvEPuo9GB0s2Uz2fxtFGMUQiZvH3jKR+9F4LwvKCc1K0E/+J4Np57JSfKgmid9QyL2r7nO19SVoVL3yBY7D8UxVIRw8sT/+JKXlnyh8roK7kaxDtW4+FMK6LT/QPvi8LkiNmA+eVv3kk9w==
    Hash of data:\nVPPxOVW2A38lCB810vuZbBH50KQaPSCouN0+tOpYDYs=
 

1 comment:

  1. Some good tips here how to load RSA keys in .NET using different approaches
    https://www.scottbrady91.com/c-sharp/rsa-key-loading-dotnet

    ReplyDelete