Team LiB
Previous Section Next Section

Public Key Encryption and Signatures

Public key encryption involves the use of a key pair. That pair consists of a secure, private key, and a public key that can be made available to anyone. This type of encryption is called asymmetric because you don't use the same key to encrypt the data that you use to decrypt the data. Data that has been encrypted with the public key can be decrypted only by the private key, and data signed (encrypted) with the private key can be verified only by using the public key.

In a typical scenario, the public key is made available to anyone, and is used to encrypt data being sent to the owner of the private key. The other main difference between secret key (symmetric) and public key (asymmetric) encryption is that public key encryption is not designed to work with varying length blocks of data. You can't link together blocks of data in a public key encryption scenario.

So, if public key encryption only works with fixed-length sets of relatively small data, how do you secure a conversation over an open wire like the Internet? The answer involves using public key encryption to encrypt a shared secret.

A typical conversation might go like this: Bob and Joe already have key pairs defined. Bob and Joe also have each other's public keys because they know they want to talk to each other. Bob decides to start a conversation by encrypting a secret key using Joe's public key. He then sends the encrypted secret key to Joe, who then decrypts it using Joe's private key. Now both Bob and Joe know a secret key that can be used for symmetric encryption of large amounts of data, and they never transmitted that secret key unencrypted over the wire. Bob and Joe are then free to exchange large files, documents, images, or just chat, with full symmetric encrypted security.


These examples will show you the basic technique for providing a validation, or digital signature, for a message transmission of some kind. The way it works is that a client uses a hash algorithm (one of the ones shown in Listings 35.235.4) to create a hash of the message. The hash of the message (called the message digest) is then encrypted using the sender's private key to create a personalized digital signature. When the receiver gets the message and the signature, the receiver decrypts the message digest using the sender's public key. The receiver then performs a hash on the message body. If the computed hash and the hash that was transmitted via public key encryption do not match, the receiver can assume that the message has been tampered with. Anyone can verify a signature (an array of bytes) because the only thing required to do so is the public key of the person who issued the signature.

The code in Listing 35.5 will show you how to verify a hash value using the DSACryptoProvider. The basic flow of code here is to create a hash. That hash is then used to create a signature. The signature can then be transmitted with the message. The receiving end creates a hash (in the sample case, just copies the previous hash) and then uses a signature deformatter to verify that the signature matches the hash.

Listing 35.5. A Demonstration of the DSACryptoServiceProvider to Verify a Signature with a Key Pair
using System;
using System.Security.Cryptography;

namespace PublicKeyCrypto
  /// <summary>
  /// Summary description for Class1.
  /// </summary>
  class Class1
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main(string[] args)
      // create a hash that we will use to sign data.
      string dataToSign = 
        "The quick brown fox pulled a sub-machine gun on the lazy dog.";

      byte[] data_Bytes = 
        System.Text.Encoding.ASCII.GetBytes( dataToSign );

      SHA1Managed sha1 = new SHA1Managed();
      byte[] hash_Bytes = sha1.ComputeHash( data_Bytes );

      // sign the hash
      DSACryptoServiceProvider dsa = 
        new DSACryptoServiceProvider(); // creates a new key pair!
      DSASignatureFormatter sigFormatter = new DSASignatureFormatter(dsa);
      byte[] signedHash = sigFormatter.CreateSignature( hash_Bytes );

      // for the sake of example, assume that the message and the signed hash have 
      // been sent across a wire and stored in the remote_* variables.
      byte[] remote_SignedHash = signedHash;
      byte[] remote_HashedValue = hash_Bytes;

      // create a signature DEformatter, point it at the original DSA provider 
      // to make sure the key pair is the same. In the 'real world', you would have
      // to see the provider with the right key
      // pair to make it able to decrypt the signature.
      DSASignatureDeformatter sigDeformatter = new DSASignatureDeformatter( dsa );
      sigDeformatter.SetHashAlgorithm( "SHA1" );

      if (sigDeformatter.VerifySignature( remote_HashedValue, remote_SignedHash ))
        Console.WriteLine("The signature used to sign the hash has been verified.");
        Console.WriteLine("The signature used to sign the hash does not match the hash.");

The only concern with this code is that it is easy to forget that you are actually working with a key pair. When you create a new instance of the DSA provider, it actually creates a public and private key pair for you that will be useable until the instance of the DSA provider is disposed of. Obviously, for production-quality code, you will need a permanent key pair for signing and verifying. Also keep in mind that the DSACryptoService provider is not an encryption provider; it is instead used for verifying signatures and hashes.


The RSACryptoServiceProvider uses public and private key pairs to encrypt and decrypt data. The following section of code shows a quick and easy (again with an auto-generated key pair) method of encrypting data with a private key:

RSACryptoServiceProvider rsa=  new RSACryptoServiceProvider();
byte[] dataToEncrypt = System.Text.Encoding.Unicode.GetBytes( "Encrypt THIS!" );
byte[] encrypted_Bytes;
byte[] decrypted_Bytes;

// no OAEP padding, as that is an XP-only service.
encrypted_Bytes = rsa.Encrypt( dataToEncrypt, false );
Console.WriteLine("RSA-encrypted data is {0} bytes long.", encrypted_Bytes.Length);
decrypted_Bytes = rsa.Decrypt( encrypted_Bytes, false );
Console.WriteLine("RSA-decrypted string is {0}.", 
System.Text.Encoding.Unicode.GetString( decrypted_Bytes ));

The code involved in creating encrypted strings is fairly simple with the RSA provider. It will always create encrypted data that is 128 bytes in length. As a result, you can't use RSA to encrypt long streams of data. Instead, it is used for encrypting keys and phrases.

    Team LiB
    Previous Section Next Section