I have previously written a number of articles on Cryptography in .NET, like the following :

Part 1 – Advanced Encryption Standard (AES)

Part 2 – RSA

Part 3 – Random Numbers and Hashes

Part 4 – Hybrid Encryption Protocols

Block Encrypter .NET Library for secure AES Encryption

In this article I will show you how to create and use Digital Signatures in .NET.

A digital signature is a mathematical scheme that demonstrates the authenticity of a message or document. A valid digital signature gives the recipient reason to believe that the message was created by a known sender, such that the sender cannot deny having sent the message (authentication and non-repudiation) and that the message was not altered in transit (integrity). Digital signatures are commonly used for software distribution, financial transactions, and in other cases where it is important to detect forgery or tampering.

Digital signatures are often used to implement a digital analog to hand written signatures. In broader terms this refers to any electronic data that carries the intent of a signature. Digital signatures employ a type of asymmetric cryptography. For messages sent through a non-secure channel, a properly implemented digital signature gives the receiver reason to believe the message was sent by the claimed sender. Digital signatures are equivalent to traditional handwritten signatures in many respects, but properly implemented digital signatures are more difficult to forge than the handwritten type. Digital signature schemes, in the sense used here, are cryptographic based, and must be implemented properly to be effective. Digital signatures can also provide non-repudiation, meaning that the signer cannot successfully claim they did not sign a message, while also claiming their private key remains secret.

A digital signature scheme consists of three algorithms

A key generation algorithm that generates a private and public key, such as RSA.

A signing algorithm that, given a message and a private key, produces a signature.

A signature verifying algorithm that, given a message, public key and a signature, either accepts or rejects the message’s claim to authenticity.

Two main properties are required. First, the authenticity of a signature generated from a fixed message and fixed private key can be verified by using the corresponding public key. Secondly, it should be computationally infeasible to generate a valid signature for a party without knowing that party’s private key. A digital signature is an authentication mechanism that enables the creator of the message to attach a code that act as a signature. It is formed by taking the hash of message and encrypting the message with creator’s private key.

public void AssignNewKey() { using (var rsa = new RSACryptoServiceProvider(2048)) { rsa.PersistKeyInCsp = false; publicKey = rsa.ExportParameters(false); privateKey = rsa.ExportParameters(true); } }

For the digital signature example project there is a class called DigialSignature. The first method in this class is AssignNewKey. This method will generate a public and private key pair to be used for creating and verifying the digital signature. Next there is the SignData method. This is the method that will create a digital signature. Let’s say you want to sign some data that represents a document, although what you are signing doesn’t matter as it is represented as a byte array. You don’t sign the actual data itself; you sign a hash of the data.

public byte[] SignData(byte[]hashOfDataToSign) { using (var rsa = new RSACryptoServiceProvider(2048)) { rsa.PersistKeyInCsp = false; rsa.ImportParameters(privateKey); var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa); rsaFormatter.SetHashAlgorithm("SHA256"); return rsaFormatter.CreateSignature(hashOfDataToSign); } }

The SignData method takes a byte array which is the hash of the data you wish to sign. The first thing that happens in this method is an instance of the RSACryptoServiceProvider class is created and the already created private key (from the AssignNewKey method) is loaded into that instance. Then an instance of RSAPKCS1SignatureFormatter is created and the instance of RSACryptoServiceProvider is passed in.

Next the hash algorithm has to be set on the RSAPKCS1SignatureFormatter instance. The algorithm set here has to match the hash algorithm you used to hash your data before creating the digital signature. For example, if you hash your data with SHA1 then the string passed into SetHashAlgorithm needs to be “SHA1”, if you used SHA256 (which this example program does), then you need to pass “SHA256” into SetHashAlgorithm.

Note: If you have a mismatch between the hash algorithm used to hash your data and the algorithm set with the SetHashAlgorithm method, a CryptographicException will be thrown with the message Bad Hash

Once this has been done, the last thing to do is call CreateSignature on the RSAPKCS1SignatureFormatter instance. This will return a byte array containing your digital signature.

The next method in the DigitalSignature class is the VerifySignature method. This method is used to verify that a digital signature is valid for a particular hash of the data you want to verify.

public bool VerifySignature(byte[]hashOfDataToSign, byte[] signature) { using (var rsa = new RSACryptoServiceProvider(2048)) { rsa.ImportParameters(publicKey); var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa); rsaDeformatter.SetHashAlgorithm("SHA256"); return rsaDeformatter.VerifySignature(hashOfDataToSign, signature); } }

The VerifySignature method takes 2 parameters, a byte array containing a hash of the data that the signature was originally created for, and a byte array of the digital signature itself. First an instance of the RSACryptoServiceProvider class is created. Then the public key that was generated with the AssignNewKey method is imported into the rsa instance.

Then an instance of the RSAPKCS1SignatureDeformatter is created and the hash algorithm is set to “SHA256”. Then to verify the signature you called VeryifySignature on the RSAPKCS1SignatureDeformatter instance but providing the hashed of the data that was signed and the actual signature itself. If the signature is valid, true is returned, and false if the signature is not valid.

Usage of this class is demonstrated in the following code block.

class Program { static void Main(string[] args) { var document = Encoding.UTF8.GetBytes("Document to Sign"); byte[] hashedDocument; using (var sha256 = SHA256.Create()) { hashedDocument = sha256.ComputeHash(document); } var digitalSignature = new DigitalSignature(); digitalSignature.AssignNewKey(); var signature = digitalSignature.SignData(hashedDocument); var verified = digitalSignature.VerifySignature(hashedDocument, signature); Console.WriteLine("Digital Signature Demonstration in .NET"); Console.WriteLine("---------------------------------------"); Console.WriteLine(); Console.WriteLine(); Console.WriteLine(" Original Text = " + System.Text.Encoding.Default.GetString(document)); Console.WriteLine(); Console.WriteLine(" Digital Signature = " + Convert.ToBase64String(signature)); Console.WriteLine(); if (verified) { Console.WriteLine("The digital signature has been correctly verified."); } else { Console.WriteLine("The digital signature has NOT been correctly verified."); } Console.ReadLine(); } }

When this program is executed and a valid digital signature is verified you will see the following output.

To prove that the VerifySignature method will return false for an invalid signature, you can deliberately tamper with the signature in the debugger by changing one of the values in the debugger, as shown in the following screenshot.

When the program is allowed to carry on executing with the tampered digital signature, you will see the following output.

The complete Digital Signature class source code is a follows :

using System; using System.IO; using System.Security.Cryptography; using System.Text; namespace CryptographyInDotNet { public sealed class DigitalSignature { private RSAParameters publicKey; private RSAParameters privateKey; public void AssignNewKey() { using (var rsa = new RSACryptoServiceProvider(2048)) { rsa.PersistKeyInCsp = false; publicKey = rsa.ExportParameters(false); privateKey = rsa.ExportParameters(true); } } public byte[] SignData(byte[] hashOfDataToSign) { using (var rsa = new RSACryptoServiceProvider(2048)) { rsa.PersistKeyInCsp = false; rsa.ImportParameters(privateKey); var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa); rsaFormatter.SetHashAlgorithm("SHA256"); return rsaFormatter.CreateSignature(hashOfDataToSign); } } public bool VerifySignature(byte[] hashOfDataToSign, byte[] signature) { using (var rsa = new RSACryptoServiceProvider(2048)) { rsa.ImportParameters(publicKey); var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa); rsaDeformatter.SetHashAlgorithm("SHA256"); return rsaDeformatter.VerifySignature(hashOfDataToSign, signature); } } } }