Monday, 16 May 2016

How to ensure the integrity of information in .NET using Digital Signature Algorithm DSA

This article will concern the topic of digital signature. There are several ways to ensure the integrity of the information or data that is sent. This concerns the concept of non-repudiation, the case that the sender cannot deny that he or she is the true sender of that data. We also can check that the data is correct, so digital signature can act as some sort of checksum - but for the entire message. We are also concerned that the information is authentic and original and not tampered with by an attacker. Digital Signature Algorithm in .NET or DSA uses the SHA-1 Secure Hash Algorithm. There are today more powerful methods to sign data, such as the RSACryptoServiceProvider. But we will in this article use DSACryptoServiceProvider. DSA is today not considered failsafe. There are security vulnerabilities. However, for ordinary use - it is not that easy to break. Just like in RSA, there is a public and private key. The API is very similar to RSACryptoServiceProvider. The following console application shows some central API calls on a DSACryptoServiceProvider.

using System;
using System.Security.Cryptography;
using System.Text;

namespace DSASignDemo
{
    class Program
    {

        // ReSharper disable once UnusedParameter.Local
        static void Main(string[] args)
        {
            var dsa = new DSACryptoServiceProvider(1024);
            var publicDsaParameters = dsa.ExportParameters(false);
            var privateDsaParameters = dsa.ExportParameters(true);
            string inputText = "Burgers and coca cola";
            byte[] inputData = Encoding.Unicode.GetBytes(inputText);
            byte[] signedBytes = SignData(inputData, privateDsaParameters);
            bool isVerified = VerifyData(inputData, signedBytes, publicDsaParameters);

            Console.WriteLine("Input text: " + inputText);
            Console.WriteLine("Signed text: " + Convert.ToBase64String(signedBytes));

            if (isVerified)
                Console.WriteLine("The message was verified");
            else
                Console.WriteLine("The message was not verified");

            byte[] hashData = ComputeHash(inputData);
            Console.WriteLine("SHA-1 computed hash: " + Convert.ToBase64String(hashData));

            bool isHashSame = CompareHash(inputText, Convert.ToBase64String(hashData)); 
            if (isHashSame)
                Console.WriteLine("Hash is the same");
            else 
                Console.WriteLine("Hash is not same");

            byte[] signedHashData = dsa.SignHash(hashData, "SHA1");

            Console.WriteLine("Signed hash: ");
            Console.WriteLine(Convert.ToBase64String(signedHashData));

            bool isVerifiedHash = dsa.VerifyHash(hashData, "SHA1", signedHashData);
            if (isVerifiedHash)
                Console.WriteLine("Hash is verified");
            else
                Console.WriteLine("Hash is not verified");



            Console.WriteLine("Press the any key to continue ..");
            Console.ReadKey();  
        }

        static bool CompareHash(string inputText, string hashText)
        {
            string computedHash = Convert.ToBase64String(ComputeHash(Encoding.Unicode.GetBytes(inputText)));
            StringComparer comparer = StringComparer.OrdinalIgnoreCase;
            return comparer.Compare(computedHash, hashText) == 0; 
        }

        static byte[] ComputeHash(byte[] inputData)
        {
            var shaManaged = new SHA1Managed();
            byte[] hashBytes = shaManaged.ComputeHash(inputData);
            return hashBytes;
        }

        static byte[] SignData(byte[] inputData, DSAParameters dsaParameters)
        {
            try
            {
                var dsa = new DSACryptoServiceProvider();
                dsa.ImportParameters(dsaParameters);
                return dsa.SignData(inputData);
            }
            catch (CryptographicException cge)
            {
                Console.WriteLine(cge.Message);
                return null;
            }
        }

        static bool VerifyData(byte[] inputData, byte[] signedData, DSAParameters dsaParmeters)
        {
            try
            {
                var dsa = new DSACryptoServiceProvider();
                dsa.ImportParameters(dsaParmeters);
                return dsa.VerifyData(inputData, signedData); 
            }
            catch (Exception err)
            {
                Console.WriteLine(err.Message);
                return false;
            }
        }




    }
}


A sample output of running this console application is:
 


Input text: Burgers and coca cola
Signed text: su7Qv+O58MyOzFjWXXx6bq9xAz9GtJ30+N8pmEYA4qFwmCdU04+qWg==
The message was verified
SHA-1 computed hash: b4o//84sCZ5cUY6cfewNia9yHYI=
Hash is the same
Signed hash:
xWExD3udQWayE2nfVDY+w8o/VuuBlKRng5Oe5XZ1zBAJO90BG+dbcA==
Hash is verified
Press the any key to continue ..


Note that the output will differ per run, where it says signed text and SHA-1 computed hash and signed hash. The reason is that the DSA algorithm will choose a random number in part of its steps and the resulting output will give different results. The boolean values here should of course be consistent, i.e. give consistens checks. Some things to note here: - Using the constructor we ask for at least a 1024 bit sized BigNum in the constructor of the DSACryptoServiceProvider to be used to generate the large primes that is involved in the DSA algorithm. - We actually use the private key of DSA to sign data and the public key to verify the data. DSA is an assymetric cryptographic algoritm and the order in which the keys are used is kind of reversed to RSA. It is the sender that sign the data and the receiver that verifies the data with a public key. - For speed, we can sometimes choose to just compute a hash like SHA-1 and then sign this hash. We can then verify hash. This is much quicker than signing large data. So first off, we can compute a SHA-1 hash, then sign the hash and then include this signed hash appended to the message, then let the receiver just verify the signed hash. The receiver will then use the hash and the signed hash and verify the hash and the fact that the message integrity is kept. We must tell the method VerifyHash which algorithm that is used. An overview of the hash algorithm names you can use in one of the arguments of SignHash and VerifyHash methods is available here: https://msdn.microsoft.com/en-us/library/system.security.cryptography.hashalgorithmname(v=vs.110)

1 comment:

  1. Do you drink Pepsi or Coke?
    ANSWER THE POLL and you could receive a prepaid VISA gift card!

    ReplyDelete