DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 5.7 Transmitting a DataSet Securely

Problem

You need to securely send a DataSet over a connection that is not secure.

Solution

Encrypt and decrypt the DataSet using the .NET cryptographic services, and serialize and save the encrypted DataSet to a stream (such as a file or network stream).

The sample code contains two event handlers:

Encrypt Button.Click

The first Button.Click creates a DataSet and encrypts it using the algorithm specified by the user and writes the encrypted DataSet to a file.

Decrypt Button.Click

The second Button.Click decrypts a file containing a DataSet previously encrypted using an algorithm specified by the user and uses the file to recreate the DataSet previously encrypted.

The C# code is shown in Example 5-7.

Example 5-7. File: SecureTransmissionForm.cs
// Namespaces, variables, and constants
using System;
using System.Configuration;
using System.Windows.Forms;
using System.Xml;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Data;
using System.Data.SqlClient;

// Table name constants
private const String ORDERS_TABLE          = "Orders";
private const String ORDERDETAILS_TABLE    = "OrderDetails";

// Relation name constants
private const String ORDERS_ORDERDETAILS_RELATION =
    "Orders_OrderDetails_Relation";

// Field name constants
private const String ORDERID_FIELD         = "OrderID";

private RSACryptoServiceProvider rSAReceiver;

private const int keySize = 128;

// DES key and IV
private Byte[] dESKey = new Byte[]
    {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
private Byte[] dESIV = new Byte[]
    {0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18};
// RC2 key and IV
private Byte[] rC2Key = new Byte[]
    {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
private Byte[] rC2IV = new Byte[]
    {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
     0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F};
// Rijndael key and IV
private Byte[] rijndaelKey = new Byte[]
    {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
     0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F};
private Byte[] rijndaelIV = new Byte[]
    {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
     0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F};
// triple DES key and IV
private Byte[] tDESKey = new Byte[]
    {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
     0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17};
private Byte[] tDESIV = new Byte[]
    {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
     0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
     0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37};

//  . . . 

[Serializable( )]
internal class EncryptedMessage
{
    public byte[] Body;        // RC2 encrypted
    public byte[] Key;         // RSA encrypted RC2 key
    public byte[] IV;          // RC2 initialization vector
}


private void encryptButton_Click(object sender, System.EventArgs e)
{
    DataSet ds = new DataSet( );
    
    SqlDataAdapter da;

    // Fill the Order table and add it to the DataSet.
    da = new SqlDataAdapter("SELECT * FROM Orders",
        ConfigurationSettings.AppSettings["Sql_ConnectString"]);
    DataTable orderTable = new DataTable(ORDERS_TABLE);
    da.FillSchema(orderTable, SchemaType.Source);
    da.Fill(orderTable);
    ds.Tables.Add(orderTable);

    // Fill the OrderDetails table and add it to the DataSet.
    da = new SqlDataAdapter("SELECT * FROM [Order Details]",
        ConfigurationSettings.AppSettings["Sql_ConnectString"]);
    DataTable orderDetailTable = new DataTable(ORDERDETAILS_TABLE);
    da.FillSchema(orderDetailTable, SchemaType.Source);
    da.Fill(orderDetailTable);
    ds.Tables.Add(orderDetailTable);

    // Create a relation between the tables.
    ds.Relations.Add(ORDERS_ORDERDETAILS_RELATION,
        ds.Tables[ORDERS_TABLE].Columns[ORDERID_FIELD],
        ds.Tables[ORDERDETAILS_TABLE].Columns[ORDERID_FIELD],
        true);

    // Clear the grid.
    dataGrid.DataSource = null;

    if(rSARadioButton.Checked)
    {
        // Asymmetric algorithm
        EncryptedMessage em = new EncryptedMessage( );

        // RC2 symmetric algorithm to encode the DataSet
        RC2CryptoServiceProvider rC2 = new RC2CryptoServiceProvider( );
        rC2.KeySize = keySize;
        // Generate RC2 Key and IV.
        rC2.GenerateKey( );
        rC2.GenerateIV( );

        // Get the receiver's RSA public key.
        RSACryptoServiceProvider rSA = new RSACryptoServiceProvider( );
        rSA.ImportParameters(rSAReceiver.ExportParameters(false));
        try
        {
            // Encrypt the RC2 key and IV with the receiver's RSA
            // public key.
            em.Key = rSA.Encrypt(rC2.Key, false);
            em.IV = rSA.Encrypt(rC2.IV, false);
        }
        catch(CryptographicException ex)
        {
            MessageBox.Show(ex.Message, "Securing Transmission",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        Cursor.Current = Cursors.WaitCursor;

        // Use the CryptoStream to write the encrypted DataSet to the
        // MemoryStream.
        MemoryStream ms = new MemoryStream( );
        CryptoStream cs = new CryptoStream(ms, rC2.CreateEncryptor( ),
            CryptoStreamMode.Write);
        ds.WriteXml(cs, XmlWriteMode.WriteSchema);
        cs.FlushFinalBlock( );
        em.Body = ms.ToArray( );

        cs.Close( );
        ms.Close( );

        // Serialize the encrypted message to a file.
        Stream s = File.Open(System.IO.Path.GetTempPath( ) +
            @"\rsa.dat", FileMode.Create);
        BinaryFormatter bf = new BinaryFormatter( );
        bf.Serialize(s, em);
        s.Close( );

        Cursor.Current = Cursors.Default;

        MessageBox.Show("Encryption complete.",
            "Securing Transmission", MessageBoxButtons.OK,
            MessageBoxIcon.Information);
    }
    else
    {
        SaveFileDialog sfd;
        sfd = new SaveFileDialog( );
        sfd.InitialDirectory = System.IO.Path.GetTempPath( );
        sfd.Filter = "All files (*.*)|*.*";
        sfd.FilterIndex = 0;

        if (sfd.ShowDialog( ) == DialogResult.OK)
        {
            FileStream fsWrite = null;
            try
            {
                fsWrite = new FileStream(sfd.FileName,
                    FileMode.Create, FileAccess.Write);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message,
                    "Securing Transmission",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                return;
            }

            Cursor.Current = Cursors.WaitCursor;

            // Symmetric algorithms
            byte[] key = null;
            byte[] iV = null;
            SymmetricAlgorithm sa = null;

            if(dESRadioButton.Checked)
            {
                sa = new DESCryptoServiceProvider( );
                key = dESKey;
                iV = dESIV;
            }
            else if(rc2RadioButton.Checked)
            {
                sa = new RC2CryptoServiceProvider( );
                sa.KeySize = 128;
                key = rC2Key;
                iV = rC2IV;
            }
            else if(rijndaelRadioButton.Checked)
            {
                sa = new RijndaelManaged( );
                key = rijndaelKey;
                iV = rijndaelIV;
            }
            else if(tripleDESRadioButton.Checked)
            {
                sa = new TripleDESCryptoServiceProvider( );          
                key = tDESKey;
                iV = tDESIV;
            }
            
            // Encrypt the DataSet
            CryptoStream cs = null;
            try
            {
                cs = new CryptoStream(fsWrite,
                    sa.CreateEncryptor(key, iV),
                    CryptoStreamMode.Write);

                ds.WriteXml(cs, XmlWriteMode.WriteSchema);
                cs.Close( );

                MessageBox.Show("Encryption complete.",
                    "Securing Transmission",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message,
                    "Securing Transmission",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
            finally
            {
                fsWrite.Close( );
                Cursor.Current = Cursors.Default;
            }
        }
    }
}

private void decryptButton_Click(object sender, System.EventArgs e)
{
    dataGrid.DataSource = null;
    DataSet ds = new DataSet( );

    if(rSARadioButton.Checked)
    {
        // Asymmetric algorithm

        // Deserialize the encrypted message from a file.
        Stream s = null;
        try
        {
            s = File.Open(System.IO.Path.GetTempPath( ) +
                @"\rsa.dat", FileMode.Open);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, "Securing Transmission",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        BinaryFormatter bf = new BinaryFormatter( );
        EncryptedMessage em = (EncryptedMessage)bf.Deserialize(s);
        s.Close( );

        // RC2 symmetric algorithm to decode the DataSet
        RC2CryptoServiceProvider rC2 = new RC2CryptoServiceProvider( );
        rC2.KeySize = keySize;

        // Decrypt the RC2 key and IV using the receiver's RSA private
        // key.
        try
        {
            rC2.Key = rSAReceiver.Decrypt(em.Key, false);
            rC2.IV = rSAReceiver.Decrypt(em.IV, false);
        }
        catch (CryptographicException ex)
        {
            MessageBox.Show(ex.Message, "Securing Transmission",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        
        Cursor.Current = Cursors.WaitCursor;

        // Put the message body into the MemoryStream.
        MemoryStream ms = new MemoryStream(em.Body);
        // Use the CryptoStream to read the encrypted DataSet from the
        // MemoryStream.
        CryptoStream cs = new CryptoStream(ms, rC2.CreateDecryptor( ),
            CryptoStreamMode.Read);            
        ds.ReadXml(cs, XmlReadMode.ReadSchema);
        cs.Close( );

        dataGrid.DataSource = ds.DefaultViewManager;

        Cursor.Current = Cursors.Default;
    }
    else
    {
        // Symmetric algorithm
        OpenFileDialog ofd;
        ofd = new OpenFileDialog( );
        ofd.InitialDirectory = System.IO.Path.GetTempPath( );
        ofd.Filter = "All files (*.*)|*.*";
        ofd.FilterIndex = 0;

        if (ofd.ShowDialog( ) == DialogResult.OK)
        {
            FileStream fsRead = null;
            try
            {
                fsRead = new FileStream(ofd.FileName,
                    FileMode.Open, FileAccess.Read);
            }
            catch(Exception ex)
            {
                dataGrid.DataSource = null;
                MessageBox.Show(ex.Message,
                    "Securing Transmission",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                return;
            }

            Cursor.Current = Cursors.WaitCursor;

            SymmetricAlgorithm sa = null;
            byte[] key = null;
            byte[] iV = null;
            if(dESRadioButton.Checked)
            {
                sa = new DESCryptoServiceProvider( );
                key = dESKey;
                iV = dESIV;
            }
            else if(rc2RadioButton.Checked)
            {
                sa = new RC2CryptoServiceProvider( );
                sa.KeySize = 128;
                key = rC2Key;
                iV = rC2IV;
            }
            else if(rijndaelRadioButton.Checked)
            {
                sa = new RijndaelManaged( );
                key = rijndaelKey;
                iV = rijndaelIV;
            }
            else if(tripleDESRadioButton.Checked)
            {
                sa = new TripleDESCryptoServiceProvider( );          
                key = tDESKey;
                iV = tDESIV;
            }

            // Decrypt the stream into the DataSet.
            CryptoStream cs = null;
            try
            {
                cs = new CryptoStream(fsRead,
                    sa.CreateDecryptor(key, iV),
                    CryptoStreamMode.Read);            
                ds.ReadXml(cs, XmlReadMode.ReadSchema);
                cs.Close( );

                dataGrid.DataSource = ds.DefaultViewManager;
            }
            catch(Exception ex)
            {
                dataGrid.DataSource = null;
                MessageBox.Show(ex.Message,
                    "Securing Transmission",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }
            finally
            {
                fsRead.Close( );
                Cursor.Current = Cursors.Default;
            }
        }
    }
}

Discussion

Cryptography protects data from being viewed or modified and provides security when transmitting or serializing the data in environments that are otherwise not secure. The data can be encrypted, transmitted or serialized in its encrypted state, and later decrypted. If the data is intercepted in its encrypted state, it is much more difficult to access the data because it is necessary to first decrypt it.

Encryption algorithms are of two types: symmetric key and asymmetric key. A brief overview follows.

Symmetric key algorithms use a secret key to both encrypt and decrypt the data. Because the same key is used both to encrypt and decrypt the data, it must be kept secret. Symmetric algorithms are also known as secret key algorithms. Symmetric key algorithms are very fast compared to asymmetric algorithms and are therefore suitable for encrypting large amounts of data. The .NET Framework classes that implement symmetric key algorithms are:

  • DESCryptoServiceProvider

  • RC2CryptoServiceProvider

  • RijndaelManaged

  • TripleDESCryptoServiceProvider

The symmetric key algorithms provided in these classes use an initialization vector (IV) in addition to the key so that an identical plaintext message produces different ciphertext when using the same key with a different IV. Good practice is to use a different IV with each encryption.

Asymmetric key algorithms use both a private key that must be kept secret and a public key that can be made available to anyone. These key pairs are used both to encrypt data (data encrypted with the public key can only be decrypted with the private key) and sign data (data signed with the private key can only be verified with the public key). The public key is used to encrypt data that is being sent to the owner of the private key while the private key is used to digitally sign data to allow the origin of communication to be verified and to ensure that those communications have not been altered. While more secure, asymmetric key algorithms are much slower than symmetric algorithms. The .NET Framework classes that implement asymmetric key algorithms are:

  • DSACryptoServiceProvider

  • RSACryptoServiceProvider

To overcome the performance limitations of asymmetric key algorithms with large amounts of data and still benefit from the much stronger security they provide, only a symmetric key is encrypted, which is in turn used to encrypt the data. Here's how it works: A public key is obtained from the person to whom the data is being sent. A symmetric key is generated by the sender and subsequently encrypted using the public key received from the recipient. The data is then encrypted using the symmetric key; this is much faster than using the public key. The encrypted key and data are then sent to the owner of the public/private key pair. The recipient uses the private key to decrypt the symmetric key and can then use the symmetric key to decrypt the data.

In the sample code, both the encryption and decryption use the key and IV values defined using variable initializers only as a convenience. While the same key and IV values must be used when encrypting and decrypting data, these values will normally be set according to the specific requirements of the application.

To encrypt the data, the user can choose between four symmetric algorithms (DES, RC2, Rijndael, and Triple DES) and one asymmetric algorithm (RSA).

If one of the symmetric algorithms is chosen, the user is presented with a file dialog to specify the file to which the encrypted DataSet is written. In each case, the specified cryptographic service provider is created, the key and IV are set, and a CryptoStream object is used to encrypt the XML representation of the DataSet, which is written to the specified file. To decrypt the file, a CryptoStream object is used to decrypt the contents of the file into an XML representation of the DataSet, which is subsequently used to recreate the DataSet.

If the asymmetric (RSA) algorithm is chosen, the sample generates both an RC2 (symmetric) key and an IV. The receiver RSACryptoServiceProvider object is created in the constructor and because the default constructor is used, a new public/private key pair is generated for the receiver each time the application is run. This means that for this example, an asymmetric encryption of the DataSet can only be decrypted while the form is open. Once the form is closed, reopening it will recreate the public/private key pair for the receiver. The ExportParameters( ) method is used to get only the public key information as an RSAParameters object from the receiver. This is imported into a new RSACryptoServiceProvider object using the ImportParameters( ) method. The Encrypt( ) method of the RSACryptoServiceProvider is then called to encrypt both the RC2 key and the IV and store them to the appropriate variables of an EncryptedMessage object, defined as an internal class. Next, the CryptoStream object is used to write the XML representation of the DataSet, encrypted using the RC2 key and IV, to a MemoryStream object, which is then stored in the Body variable of the EncryptedMessage object. Finally, the EncryptedMessage is serialized to a file.

The decryption of the encrypted file is just the reverse of the encryption process. The file is deserialized to an EncryptedMessage object. The Decrypt( ) method of the receiver RSACryptoServiceProvider object is used to decrypt the RC2 key and IV using the receiver's private key. The CryptoStream object is then used to decrypt the XML for the DataSet stored in the Body variable of the EncryptedMessage object. The DataSet is recreated from the XML.

Although this example demonstrates serializing the encrypted DataSet to a file, you can use the same technique to serialize the DataSet to a stream so that it can be transmitted securely in an environment that is otherwise not secure.

    [ Team LiB ] Previous Section Next Section