Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional Visual Studio 2005 (2006) [eng]

.pdf
Скачиваний:
117
Добавлен:
16.08.2013
Размер:
21.9 Mб
Скачать

Chapter 16

SecureString

It’s often necessary to prompt users for a password, which is typically held in a String variable. Any information held in the String will be contained within the String table. Because the information is stored in an unencrypted format, it can potentially be extracted from memory. To compound the problem, due to the immutable nature of the String class, there is no way to programmatically remove the information from memory. Using the String class to work with private encryption keys can be considered a security weakness.

An alternative is to use the SecureString class. Unlike the String class, the SecureString class is not immutable, so the information can be modified and cleared after use. The information is also encrypted, so it can be retrieved from memory. Because you never want the unencrypted form of the information to be visible, there is no way to retrieve a String representation of the encrypted data. The following sample code inherits from the standard TextBox control to create a SecureTextbox that will ensure that the password entered is never available as an unencrypted string in memory:

Imports System.Security

Imports System.Windows.Forms

Public Class SecureTextbox

Inherits TextBox

Private Const cHiddenCharacter As Char = “*”c

Private m_SecureText As New SecureString

Public Property SecureText() As SecureString

Get

Return m_SecureText

End Get

Set(ByVal value As SecureString)

If value Is Nothing Then

Me.m_SecureText.Clear()

Else

Me.m_SecureText = value

End If

End Set

End Property

Private Sub RefreshText(Optional ByVal index As Integer = -1)

Me.Text = New String(cHiddenCharacter, Me.m_SecureText.Length)

If index < 0 Then

Me.SelectionStart = Me.Text.Length

Else

Me.SelectionStart = index

End If

End Sub

Private Sub SecureTextbox_KeyPress(ByVal sender As Object, _ ByVal e As KeyPressEventArgs) _

Handles Me.KeyPress

If Not Char.IsControl(e.KeyChar) Then

If Me.SelectionStart >= 0 And Me.SelectionLength > 0 Then For i As Integer = Me.SelectionStart To _

216

Cryptography

(Me.SelectionStart + Me.SelectionLength) - 1

Me.m_SecureText.RemoveAt(Me.SelectionStart)

Next

End If

End If

Select Case e.KeyChar Case Chr(Keys.Back)

If Me.SelectionLength = 0 and Me.SelectionStart > 0 Then

‘If nothing selected, then just backspace a single character Me.m_SecureText.RemoveAt(Me.SelectionStart - 1)

End If

Case Chr(Keys.Delete)

If Me.SelectionLength = 0 and _

Me.SelectionStart < Me.m_SecureText.Length Then Me.m_SecureText.RemoveAt(Me.SelectionStart)

End If Case Else

Me.m_SecureText.InsertAt(Me.SelectionStart, e.KeyChar) End Select

e.Handled = True RefreshText(Me.SelectionStart + 1)

End Sub End Class

The SecureTextbox traps each KeyPress and adds any characters to the underlying SecureString. The Text property is updated to contain a String of * characters that is the same length as the SecureString. Once the text has been entered into the text box, the SecureString could be used to initiate another process, as shown in the following example:

Private Sub BtnStartNotepad_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _

Handles btnStartNotePad.Click

Dim psi As New ProcessStartInfo() psi.Password = Me.SecureTextbox1.SecureText

psi.UserName = Me.cboUsers.SelectedItem.ToString psi.UseShellExecute = False

psi.FileName = “notepad”

Dim p As New Process() p.StartInfo = psi p.Start()

End Sub

Key Containers

In the example application you have just worked through, both Susan and David have an asymmetric key pair, of which the public key was shared. Using this information, they shared a symmetric key that was used as a session key for transmitting data between parties. Given the limitations around authentication of a symmetric key once it has been shared to multiple parties, it is not advisable to maintain the same key for an extended period. Instead, a new symmetric key should be established for each transmission session.

217

Chapter 16

Asymmetric key pairs, conversely, can be stored and reused to establish each new session. Given that only the public key is ever distributed, the chance of the private key falling into the wrong hands is greatly reduced. However, there is still a risk that the private key might be retrieved from the local computer if it is stored in an unencrypted format. This is where a key container can be used to preserve the key pair between sessions.

Working with a key container is relatively straightforward. Instead of importing and exporting the key information using methods such as ToXMLString and FromXMLString, you indicate that the asymmetric algorithm provider should use a key container by specifying a CspParameters class in the constructor. The following code snippet retrieves an instance of the AysmmetricAlgorithm class by specifying the container name. If no existing key pair exists in a container with that name, a new pair will be created and saved to a new container with that name:

Private Sub BtnLoadMyKeyPair_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _

Handles BtnLoadMyKeyPair.Click

Dim algorithm As AsymmetricAlgorithm = _ LoadAsymmetricAlgorithm(Me.TxtKeyContainerName.Text)

End Sub

Private Function LoadAsymmetricAlgorithm(ByVal container As String) _

As AsymmetricAlgorithm

‘Create the CspParameters object using the container name Dim cp As New CspParameters()

cp.KeyContainerName = container

‘Create or load the key information from the container Dim rsa As New RSACryptoServiceProvider(cp)

Return rsa End Function

If you need to remove a key pair from a key container, follow the same process to create the

AsymmetricAlgorithm. You then need to set the PersistKeyInCsp to False, and execute the Clear method. This will ensure that the key is removed from both the key container and the

AsymmetricAlgorithm object.

Summar y

This chapter demonstrated how cryptography can play an important role in establishing a secure communication channel between multiple parties. Multiple steps are required to set up this channel, using a combination of symmetric and asymmetric algorithms. When deciding on a security scheme for your application, it is important to remember the four goals of cryptography: authentication, non-repudiation, integrity, and confidentiality. Not all applications require that all of these goals be achieved, and a piecemeal approach might be necessary to balance performance and usability against security.

Now that you have seen how to protect the data in your application, the next chapter shows you how to protect the embedded logic within your application from being stolen by a competitor.

218

Obfuscation

After wading through all the hype about the .NET Framework, you will have picked up on the fact that instead of compiling to machine language, .NET languages are compiled into the Microsoft Intermediary Language (MSIL, or just IL, for short). The IL is then just-in-time compiled as it is required for execution. This two-stage approach has a number of significant advantages, such as being able to dynamically query an assembly for a type and method information using reflection. However, this is a double-edged sword because this same flexibility means that once-hidden algorithms and business logic can easily be reverse engineered, legally or otherwise. This chapter introduces obfuscation and how it can be used to protect your application logic. Be forewarned, however: Obfuscation provides no guarantees, as the IL must still be executable and can thus be analyzed and potentially decompiled.

MSIL Disassembler

Before looking at how you can protect your code from other people, this section describes a couple of tools that can help you build better applications. The first tool is the MSIL Disassembler, or IL Dasm, which is installed with the .NET Framework SDK and can be found by clicking Start All Programs Microsoft .NET Framework SDK v2.0 Tools. In Figure 17-1, a small application has been opened using this tool, and you can immediately see the namespace and class information contained within this assembly.

Chapter 17

Figure 17-1

To compare the IL that is generated, the original source code for the MathematicalGenius class is as follows:

Namespace SourceLibrary

Public Class MathematicalGenius

Public Shared Function GenerateMagicNumber _

(ByVal age As Integer, ByVal height As Integer) As Integer

Return age * height

End Function

End Class

End Namespace

Double-clicking on the GenerateMagicNumber method in IL Dasm will open up an additional window that shows the IL for that method. Figure 17-2 shows the IL for the GenerateMagicNumber method, which represents your patented algorithm. In actual fact, as you can roughly make out from the IL, the method expects two int32 parameters, age and height, and multiplies them.

Figure 17-2

Anyone with a background in assembly programming will be at home reading the IL. For everyone else, a decompiler can convert this IL back into one or more .NET languages.

220

Obfuscation

Decompilers

One of the most widely used decompilers is Reflector by Lutz Roeder (available for download at www.aisto.com/roeder/dotnet/). Reflector can be used to decompile any .NET assembly into C#, VB.NET, and even Delphi. In Figure 17-3, the same assembly you just accessed is opened using IL Dasm, in Reflector.

Figure 17-3

In the pane on the left of Figure 17-3, you can see the namespaces, type, and method information in a similar layout to IL Dasm. Double-clicking a method should open the Disassembler pane on the right, which will display the contents of that method in the language specified in the toolbar. In this case, you can see the VB.NET code that generates the magic number, which is almost identical to the original code.

If the generation of the magic number were a real secret on which your organization made money, the ability to decompile this application would pose a significant risk. This is made worse when you add the File Disassembler add-in, written by Denis Bauer (available at www.denisbauer.com/NETTools/ FileDisassembler.aspx). With this add-in, an entire assembly can be decompiled into source files, complete with project file. There are some compatibility issues with the VB.NET output, but the generated C# can usually be compiled and executed without any adjustments.

221

Chapter 17

Obfuscating Your Code

So far, this chapter has highlighted the need for better protection for the logic that is embedded in your applications. Obfuscation is the art of renaming symbols in an assembly so that the logic is unintelligible and can’t be easily decompiled. Numerous products can obfuscate your code, each using its own tricks to make the output less likely to be decompiled. Visual Studio 2005 ships with the Community edition of Dotfuscator, which this chapter uses as an example of how you can apply obfuscation to your code.

Dotfuscator

Although Dotfuscator can be launched from the Tools menu within Visual Studio 2005, it is a separate product with its own licensing. The Community edition contains only a subset of the functionality of the Standard and Professional versions of the product. If you are serious about trying to hide the functionality embedded in your application, you should consider upgrading.

After starting Dotfuscator from the Tools menu, it prompts you to either create a new project or use an existing one. As Dotfuscator uses its own project format, create a new project that will be used to track which assemblies you are obfuscating and any options that you specify. Into the blank project, add the

.NET assemblies that you want to obfuscate. Unlike other build activities that are typically run based on source files, obfuscating takes existing assemblies, applies the obfuscation algorithms, and generates a set of new assemblies. Figure 17-4 shows a new Dotfuscator project into which has been added the assembly for the ObfuscationSample application.

Figure 17-4

Without needing to adjust any other settings, you can select Build from the File menu, or click the play button (fourth from the left) on the toolbar, to obfuscate this application. The obfuscated assemblies will typically be added to a Dotfuscated folder. If you open this assembly using Reflector, as shown in Figure 17-5, you will notice that the GenerateMagicNumber method has been renamed, along with the input parameters. In addition, the namespace hierarchy has been removed and classes have been renamed. Although this is a rather contrived example, you can see how numerous methods with the same, or similar, non-intuitive names could cause confusion and make it difficult to decompile.

222

Obfuscation

Figure 17-5

Unfortunately, this example obfuscated a public method. If you were to reference this assembly in another application, you would see a list of classes that have no apparent structure, relationship, or even naming convention. This would make working with this assembly very difficult. Luckily, Dotfuscator enables you to control what is renamed. Before going ahead, you need to refactor the code slightly to pull the functionality out of the public method. If you didn’t do this and you excluded this method from being renamed, your secret algorithm would not be obfuscated. By separating the logic into another method, you can obfuscate that while keeping the public interface. The refactored code would look like the following:

Namespace SourceLibrary

Public Class MathematicalGenius

Public Shared Function GenerateMagicNumber _

(ByVal age As Integer, ByVal height As Integer) As Integer

Return MultiplyAgeAndHeight(age, height)

End Function

Private Shared Function MultiplyAgeAndHeight _

(ByVal age As Integer, ByVal height As Integer) As Integer

Return age * height

End Function

End Class

End Namespace

223

Chapter 17

After rebuilding the application and refreshing the Dotfuscator project (because there is no Refresh button, you need to reopen the project by selecting it from the Recent Projects list), the Rename tab will look like the one shown in Figure 17-6.

Figure 17-6

In the left pane you can see the familiar tree view of your assembly, with the attributes, namespaces, types, and methods listed. As the name of the tab suggests, this tree enables you to exclude symbols from being renamed. In Figure 17-6, the GenerateMagicNumber method, as well as the class that it is contained in, is excluded (otherwise, you would have ended up with something like b. Generate MagicNumber, where b is the renamed class). On the Options tab you also need to check the Keep Namespace checkbox. When you build the Dotfuscator project and look in the Output tab, you will see that the MathematicalGenius class and the GenerateMagicNumber method have not been renamed, as shown in Figure 17-7.

Figure 17-7

224

Obfuscation

The MultiplyAgeAndHeight method has been renamed to a, as indicated by the subnode with the Dotfuscator icon.

Words of Caution

There are a couple of places where it is worth considering what will happen when obfuscation occurs, and how it will affect the workings of the application.

Reflection

The .NET Framework provides a rich reflection model through which types can be queried and instantiated dynamically. Unfortunately, some of the reflection methods use string lookups for type and method names. Clearly, the use of obfuscation will prevent these methods from working, and the only solution is not to mangle any symbols that may be invoked using reflection. Dotfuscator will attempt to determine a limited set of symbols to exclude based on how the reflection objects are used. For example, if you were to dynamically create an object based on the name of the class, and you then cast that object to a variable that matches an interface the class implements, Dotfuscator would be able to limit the excluded symbols to include only types that implemented that interface.

Strongly Named Assemblies

One of the purposes behind giving an assembly a strong name is that it prevents the assembly from being tampered with. Unfortunately, obfuscating relies on being able to take an existing assembly and mangle the names and code flow, before generating a new assembly. This would mean that the assembly is no longer strongly named. To allow obfuscation to occur you need to delay signing of your assembly by checking the Delay Sign Only checkbox on the Signing tab of the project properties window, as shown in Figure 17-8.

Figure 17-8

225