Microsoft Visual C# 2005 Unleashed
Hashes and digital signatures work hand in hand in order to give your code the ability to sign data as well as verify that the data has not been tampered with. The sequence of events typically works like this:
Let's take a look at the first half of the process, the hashing of data and the creation of a signature from that hash: string verifiableMesage = "It was the best of times, it was the worst of times."; SHA1Managed sha = new SHA1Managed(); byte[] hashValue = sha.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(verifiableMesage)); // now that we have a hash, create a signature based on the hash // re-use Jane's key. StreamReader sr = File.OpenText(@"..\..\..\..\JanesKey.xml"); string janesKey = sr.ReadToEnd(); RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(janesKey); RSAPKCS1SignatureFormatter sigFormatter = new RSAPKCS1SignatureFormatter(rsa); sigFormatter.SetHashAlgorithm("SHA1"); byte[] signedHash = sigFormatter.CreateSignature(hashValue); // write the signed hash to disk FileStream fs = new FileStream(@"..\..\..\..\signedHash.dat", FileMode.Create); fs.Write(signedHash, 0, signedHash.Length) ; fs.Close();
Next, we'll need to write some code that reads the signed hash from the data file, computes its own hash, and does a comparison to verify the authenticity of the data. The following is the code contained in a console application that does this: string verifiableMesage = "It was the best of times, it was the worst of times."; string wrongMessage = "It was the best of times it was the worst of times"; SHA1Managed sha = new SHA1Managed(); byte[] verifiableMessageHash = sha.ComputeHash( System.Text.ASCIIEncoding.ASCII.GetBytes(verifiableMesage)); byte[] wrongMessageHash = sha.ComputeHash( System.Text.ASCIIEncoding.ASCII.GetBytes(wrongMessage)); // note that we never manually decrypt the sig, that's done by the sig verifier FileStream fs = new FileStream(@"..\..\..\..\signedHash.dat", FileMode.Open); byte[] fileHash = new byte[fs.Length]; fs.Read(fileHash, 0, (int)fs.Length) ; fs.Close(); // get jane's key so we can decrypt the hash StreamReader sr = File.OpenText(@"..\..\..\..\JanesKey.xml"); string janesKey = sr.ReadToEnd(); sr.Close(); RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(janesKey); RSAPKCS1SignatureDeformatter sigDeformatter = new RSAPKCS1SignatureDeformatter(rsa); sigDeformatter.SetHashAlgorithm("SHA1"); if (sigDeformatter.VerifySignature(verifiableMessageHash, fileHash)) { Console.WriteLine("The real message is verified as untampered.") ; } if (sigDeformatter.VerifySignature(wrongMessageHash, fileHash) == false) { Console.WriteLine("The fake message is verified as fake."); } else { Console.WriteLine("This should statistically never happen."); } Console.ReadLine(); The first thing that this code does is create hashes from two different strings. One string is the original and valid data, whereas the other string is missing a comma. If all that I've said about hashing and signatures is true, the missing comma should cause a signed hash validation to fail, simulating data that has been tampered with. Next the key is loaded from the XML file (in a real-world scenario, the key would probably be coming from an OS-level key container). A signature deformatter is then loaded based on the SHA-1 hashing algorithm. Finally, two comparisons are made: one comparison against the legitimate hash, and one against the hash that simulates tampered data. When we run the application, the output confirms that the digital signature is doing its job: The real message is verified as untampered. The fake message is verified as fake. |