JavaScript Editor JavaScript Editor     JavaScript Debugger 



Team LiB
Previous Section Next Section

Hiding Information with Encryption

In the previous example, cryptography is used to assist in user authentication. However, no steps are taken to hide data as it flows over the wire. Malicious users can eavesdrop and discover valuable information such as the ObjRef (where a client can be reached), or the e-mails of users that are currently online, and so on. The same problem occurs with communication between peers. Currently, messages flow over the network as plain text, which is visible to any user in the right place with a network sniffer.

You can solve this problem by adding a new class to the cryptography component, which you can use on both the client and web-server end. This is the EncryptedObject class.

The EncryptedObject Class

In adding an encryption solution, you can use the same approach we used for signing data. In this case, you'll need a dedicated class, which we'll name EncryptedObject. The methods exposed by this class are quite similar to those provided by the SignedObject class, but the code involved is somewhat more complicated. This is because when you use asymmetric encryption you must encrypt data one block at a time. If you need to encrypt data that's larger than one block, you must divide it into multiple blocks, encrypt each one individually, and piece the encrypted blocks back together.

Here's an overview of how you would use the EncryptedObject:

  1. First, create and configure a serializable object.

  2. Create the EncryptedObject class. The EncryptedObject class provides a constructor that takes any object, along with the public key XML (which should be the public key of the recipient). This constructor serializes the object, encrypts it, and stores it in an internal member variable.

  3. You can then convert the encrypted object into a byte array through .NET serialization using the Serialize() method. This is the data you'd send to the other peer.

  4. The recipient deserializes the byte array into an EncryptedObject, using the shared Deserialize() method.

  5. The recipient calls the DecryptContainedObject() method with its private key to retrieve the original object.

The EncryptedObject code is shown here. The Serialize() and Deserialize() methods are omitted, because they're identical to those used in the SignedObject class.

<Serializable()> _
Public Class EncryptedObject

    Private SerializedObject As New MemoryStream()

    Public Sub New(ByVal objectToEncrypt As Object, ByVal publicKeyXml As String)

        ' Serialize a copy of objectToEncrypt in memory.
        Dim f As New BinaryFormatter()
        Dim ObjectStream As New MemoryStream()
        f.Serialize(ObjectStream, objectToEncrypt)
        ObjectStream.Position = 0

        Dim Rsa As New RSACryptoServiceProvider()
        Rsa.FromXmlString(publicKeyXml)
        ' The block size depends on the key size.
        Dim BlockSize As Integer
        If Rsa.KeySize = 1024 Then
            BlockSize = 16
        Else
            BlockSize = 5
        End If

        ' Move through the data one block at a time.
        Dim RawBlock(), EncryptedBlock() As Byte
        Dim i As Integer
        Dim Bytes As Integer = ObjectStream.Length
        For i = 0 To Bytes Step BlockSize

            If Bytes - i > BlockSize Then
                ReDim RawBlock(BlockSize - 1)
            Else
                ReDim RawBlock(Bytes - i - 1)
            End If

            ' Copy a block of data.
            ObjectStream.Read(RawBlock, 0, RawBlock.Length)

            ' Encrypt the block of data.
            EncryptedBlock = Rsa.Encrypt(RawBlock, False)

            ' Write the block of data.
            Me.SerializedObject.Write(EncryptedBlock, 0, EncryptedBlock.Length)
        Next

    End Sub

    ' (Serialize and Deserialize methods omitted.)

    Public Function DecryptContainedObject(ByVal keyPairXml As String) As Object

        Dim Rsa As New RSACryptoServiceProvider()
        Rsa.FromXmlString(keyPairXml)

        ' Create the memory stream where the decrypted data
        ' will be stored.
        Dim ObjectStream As New MemoryStream()
        'Dim ObjectBytes() As Byte = Me.SerializedObject.ToArray()
        Me.SerializedObject.Position = 0
        ' Determine the block size for decrypting.
        Dim keySize As Integer = Rsa.KeySize / 8

        ' Move through the data one block at a time.
        Dim DecryptedBlock(), RawBlock() As Byte
        Dim i As Integer
        Dim Bytes As Integer = Me.SerializedObject.Length
        For i = 0 To bytes - 1 Step keySize

            If ((Bytes - i) > keySize) Then
                ReDim RawBlock(keySize - 1)
            Else
                ReDim RawBlock(Bytes - i - 1)
            End If

            ' Copy a block of data.
            Me.SerializedObject.Read(RawBlock, 0, RawBlock.Length)

            ' Decrypt a block of data.
            DecryptedBlock = Rsa.Decrypt(RawBlock, False)

            ' Write the decrypted data to the in-memory stream.
            ObjectStream.Write(DecryptedBlock, 0, DecryptedBlock.Length)
        Next

        ObjectStream.Position = 0
        Dim f As New BinaryFormatter()
        Return f.Deserialize(ObjectStream)

    End Function

End Class

Sending and Receiving Encrypted Messages

Now, you only need to make minor changes to the ClientProcess class in order to use encryption with the EncryptedObject class. First, you need to define a Message class that will contain the information that's being sent:


<Serializable()> _
Public Class Message

    Public SenderAlias As String
    Public MessageBody As String

    Public Sub New(ByVal sender As String, ByVal body As String)
        Me.SenderAlias = sender
        Me.MessageBody = body
    End Sub

End Class

You also need to modify the ITalkClient interface:

Public Interface ITalkClient

    ' The server calls this to forward a message to the appropriate client.
    Sub ReceiveMessage(ByVal encryptedMessage As EncryptedObject)

End Interface

When sending a message, you need to construct a Message object and encrypt it. You don't need to use the Serialize() method to convert it to a byte stream because the .NET Remoting infrastructure can automatically convert serializable types for you. The full code is shown here, with the modified lines highlighted in bold. Note that the public XML information is retrieved from the web service as needed for the peer.

Public Sub SendMessage(ByVal emailAddress As String, ByVal messageBody As String)

    Dim PeerInfo As localhost.PeerInfo

    ' Check if the peer-connectivity information is cached.
    If RecentClients.Contains(emailAddress) Then
        PeerInfo = CType(RecentClients(emailAddress), localhost.PeerInfo)
    Else
        PeerInfo = DiscoveryService.GetPeerInfo(emailAddress)
        RecentClients.Add(PeerInfo.EmailAddress, PeerInfo)
    End If

    Dim ObjStream As New MemoryStream(PeerInfo.ObjRef)
    Dim f As New BinaryFormatter()
    Dim Obj As Object = f.Deserialize(ObjStream)
    Dim Peer As ITalkClient = CType(Obj, ITalkClient)

    Dim Message As New Message(Me.Alias, messageBody)
    Dim Package As New EncryptedObject(Message, PeerInfo.PublicKeyXml)

    Try
        Peer.ReceiveMessage(Package)
    Catch
        ' Ignore connectivity errors.
    End Try

End Sub

When receiving a message, the peer simply decrypts the contents using its private key.

Private Sub ReceiveMessage(ByVal encryptedMessage As EncryptedObject) _
  Implements ITalkClient.ReceiveMessage

    Dim Message As Message
    Message = CType(encryptedMessage.DecryptContainedObject( _
      Me.Rsa.ToXmlString(True)), Message)
    RaiseEvent MessageReceived(Me, _
      New MessageReceivedEventArgs(Message.MessageBody, Message.SenderAlias))

End Sub

The same technique can be applied to protect any data. For example, you could (and probably should) use it to encrypt messages exchanged between the client and discovery service.

Chaining Encryption and Signing

The designs of the EncryptedObject and SignedObject classes lend themselves particularly well to being used together. For example, you can create a signed, encrypted message by wrapping a Message object in an EncryptedObject, and then wrapping the EncryptedObject in a SignedObject. (You could also do it the other way around, but the encrypt-and-sign approach is convenient because it allows you to validate the signature before you perform the decryption.)

Figure 11-5 diagrams this process.

Click To expand
Figure 11-5: Encrypting and signing a message

Here's the code you would use to encrypt and sign the message:

Dim Message As New Message(Me.Alias, messageBody)

' Encrypt the message using the recipient's public key.
Dim EncryptedPackage As New EncryptedObject(Message, PeerInfo.PublicKeyXml)

' Sign the message with the sender's private key.
Dim SignedPackage As New SignedObject(Message, Me.Rsa.ToXmlString(True))

Try
    Peer.ReceiveMessage(SignedPackage)
Catch
    ' Ignore connectivity errors.
End Try

The recipient would then validate the signature, deserialize the encrypted object, and then decrypt it:

' Verify the signature.
If Not encryptedPackage.ValidateSignature(PeerInfo.PublicKeyXml) Then
    ' Ignore this message.
Else
    Dim EncryptedMessage As EncryptedObject
    EncryptedMessage = CType(encryptedPackage.GetObjectWithoutSignature, _
      EncryptedObject)

    ' Decrypt the message.
    Dim Message As Message
    Message = CType(EncryptedMessage.DecryptContainedObject( _
      Me.Rsa.ToXmlString(True)), Message)
    RaiseEvent MessageReceived(Me, _
      New MessageReceivedEventArgs(Message.MessageBody, Message.SenderAlias))

End If

Using Session Keys

There's one other enhancement that you might want to make to this example. As described earlier, asymmetric encryption is much slower than symmetric encryption. In the simple message-passing example this won't make much of a difference, but if you need to exchange larger amounts of data it becomes much more important.

In this case, the solution is to use symmetric encryption. However, because both peers won't share a symmetric key, you'll have to create one dynamically and then encrypt it asymmetrically. The recipient will use its private key to decrypt the symmetric key, and then use the symmetric key to decrypt the remainder of the message.

This pattern is shown, in abbreviated form, with the following LargeEncryptedObject class. It includes the code used to encrypt the serializable object, but leaves out the asymmetric encryption logic used to encrypt the dynamic symmetric key for brevity. The code used for symmetric encryption is much shorter, because it can use a special object called the CryptoStream. The CryptoStream manages blockwise encryption automatically and can be used to wrap any other .NET stream object. For example, you can use a CryptoStream to perform automatic encryption before data is sent to a FileStream, or perform automatic decryption as it is read to memory. In the case of the LargeEncryptedObject, the CryptoStream wraps another memory stream.

<Serializable()> _
Public Class LargeEncryptedObject

    Private SerializedObject As New MemoryStream()
    Private EncryptedDynamicKey() As Byte

    Public Sub New(ByVal objectToEncrypt As Object, ByVal publicKeyXml As String)

        ' Generate the new symmetric key.
        ' In this example, we'll use the Rijndael algorithm.
        Dim Rijn As New RijndaelManaged()
        ' Encrypt the RijndaelManaged.Key and RijndaelManaged.IV properties.
        ' Store the data in the EncryptedDynamicKey member variable.
        ' (Asymmetric encryption code omitted.)

        ' Write the data to a stream that encrypts automatically.
        Dim cs As New CryptoStream(Me.SerializedObject,_
          Rijn.CreateEncryptor(), CryptoStreamMode.Write)

        ' Serialize and encrypt the object in one step using the CryptoStream.
        Dim f As New BinaryFormatter()
        f.Serialize(cs, objectToEncrypt)

        ' Write the final block.
        cs.FlushFinalBlock()

    End Sub

    Public Function DecryptContainedObject(ByVal keyPairXml As String) As Object

        ' Generate the new symmetric key.
        Dim Rijn As New RijndaelManaged()

        ' Decrypt the EncryptedDynamic key member variable, and use it to set
        ' the RijndaelManaged.Key and RijndaelManaged.IV properties.
        ' (Asymmetric decryption code omitted.)

        ' Write the data to a stream that decrypts automatically.
        Dim ms As New MemoryStream()
        Dim cs As New CryptoStream(ms, Rijn.CreateDecryptor(), _
          CryptoStreamMode.Write)

        ' Decrypt the object 1 KB at a time.
        Dim i, BytesRead As Integer
        Dim Bytes(1023) As Byte
        For i = 0 To Me.SerializedObject.Length
            BytesRead = Me.SerializedObject.Read(Bytes, 0, Bytes.Length)
            cs.Write(Bytes, 0, BytesRead)
        Next

        ' Write the final block.
        cs.FlushFinalBlock()
        ' Now deserialize the decrypted memory stream.
        ms.Position = 0
        Dim f As New BinaryFormatter()
        Return f.Deserialize(ms)

    End Function

    ' (Serialize and Deserialize methods omitted.)

End Class

A full description of the .NET cryptography classes and the CryptoStream is beyond the scope of this book.


Team LiB
Previous Section Next Section


JavaScript Editor Free JavaScript Editor     JavaScript Editor