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

Visual CSharp 2005 Recipes (2006) [eng]

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

428C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

VerifyHexHash: This method converts a new hash code (a byte array) to a hexadecimal string for comparison to an old hash code. Other than the BitConverter.ToString method, the .NET Framework class library does not provide an easy method to convert a byte array to a hexadecimal string. You must program a loop to step through the elements of the byte array, convert each individual byte to a string, and append the string to the hexadecimal string representation of the hash code. The use of a System.Text.StringBuilder avoids the unnecessary creation of new strings each time the loop appends the next byte value to the result string. (See recipe 2-1 for more details.)

VerifyB64Hash: This method takes a new hash code as a byte array and the old hash code as a Base64-encoded string. The method encodes the new hash code as a Base64 string and performs a straightforward string comparison of the two values.

VerifyByteHash: This method compares two hash codes represented as byte arrays. The .NET Framework class library does not include a method that performs this type of comparison, and so you must program a loop to compare the elements of the two arrays. This code uses a few timesaving techniques, namely ensuring that the byte arrays are the same length before starting to compare them and returning false on the first difference found.

using System; using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter11

{

class Recipe11_16

{

//A method to compare a newly generated hash code with an

//existing hash code that's represented by a hex code string public static bool VerifyHexHash(byte[] hash, string oldHashString)

{

//Create a string representation of the hash code bytes. StringBuilder newHashString = new StringBuilder(hash.Length);

//Append each byte as a two-character uppercase hex string. foreach (byte b in hash)

{

newHashString.AppendFormat("{0:X2}", b);

}

//Compare the string representations of the old and new hash

//codes and return the result.

return (oldHashString == newHashString.ToString());

}

//A method to compare a newly generated hash code with an

//existing hash code that's represented by a Base64-encoded string. private static bool VerifyB64Hash(byte[] hash, string oldHashString)

{

//Create a Base64 representation of the hash code bytes. string newHashString = Convert.ToBase64String(hash);

//Compare the string representations of the old and new hash

//codes and return the result.

return (oldHashString == newHashString);

}

C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

429

//A method to compare a newly generated hash code with an

//existing hash code represented by a byte array.

private static bool VerifyByteHash(byte[] hash, byte[] oldHash)

{

//If either array is null or the arrays are different lengths,

//then they are not equal.

if (hash == null || oldHash == null || hash.Length != oldHash.Length) return false;

//Step through the byte arrays and compare each byte value. for (int count = 0; count < hash.Length; count++)

{

if (hash[count] != oldHash[count]) return false;

}

//Hash codes are equal.

return true;

}

}

}

11-17. Ensure Data Integrity Using a Keyed

Hash Code

Problem

You need to transmit a file to someone and provide the recipient with a means to verify the integrity of the file and its source.

Solution

Share a secret key with the intended recipient. This key would ideally be a randomly generated number, but it could also be a phrase that you and the recipient agree to use. Use the key with one of the keyed hashing algorithm classes derived from the System.Security.Cryptography.KeyedHashAlgorithm class to create a keyed hash code. Send the hash code with the file. On receipt of the file, the recipient will generate the keyed hash code of the file using the shared secret key. If the hash codes are equal, the recipient knows that the file is from you and that it has not changed in transit.

How It Works

Hash codes are useful for comparing two pieces of data to determine if they are the same, even if you no longer have access to the original data. However, you cannot use a hash code to reassure the recipient of data as to the data’s integrity. If someone could intercept the data, that person could replace the data and generate a new hash code. When the recipient verifies the hash code, it will seem correct, even though the data is actually nothing like what you sent originally.

A simple and efficient solution to the problem of data integrity is a keyed hash code. A keyed hash code is similar to a normal hash code (discussed in recipes 11-14 and 11-15); however, the keyed hash code incorporates an element of secret data—a key—known only to the sender and the receiver. Without the key, a person cannot generate the correct hash code from a given set of data. When you successfully verify a keyed hash code, you can be certain that only someone who knows the secret key could generate the hash code.

430 C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

Caution The secret key must remain secret. Anyone who knows the secret key can generate valid keyed hash codes, meaning that you would be unable to determine if someone else who knew the key had changed the content of a document. For this reason, you should not transmit or store the secret key with the document whose integrity you are trying to protect.

Generating keyed hash codes is similar to generating normal hash codes. The abstract class

System.Security.Cryptography.KeyedHashAlgorithm extends the class System.Security.Cryptography. HashAlgorithm and provides a base class from which all concrete keyed hashing algorithm implementations must derive. The .NET Framework class library includes the seven keyed hashing algorithm implementations listed in Table 11-5. Each implementation is a member of the namespace System.Security.Cryptography.

Table 11-5. Keyed Hashing Algorithm Implementations

Algorithm/Class Name

Key Size (in Bits)

Hash Code Size (in Bits)

HMACMD5 (new in .NET 2.0)

Any

128

HMACRIPEMD160 (new in .NET 2.0)

Any

160

HMACSHA1

Any

160

HMACSHA256 (new in .NET 2.0)

Any

256

HMACSHA384 (new in .NET 2.0)

Any

384

HMACSHA512 (new in .NET 2.0)

Any

512

MACTripleDES

128, 192

64

 

 

 

As with the standard hashing algorithms, you can either create keyed hashing algorithm objects directly or use the static factory method KeyedHashAlgorithm.Create and pass the algorithm name as an argument. Using the factory approach allows you to write generic code that can work with any keyed hashing algorithm implementation, but as shown in Table 11-5, MACTripleDES supports fixed key lengths that you must accommodate in generic code.

If you use constructors to instantiate a keyed hashing object, you can pass the secret key to the constructor. Using the factory approach, you must set the key using the Key property inherited from the KeyedHashAlgorithm class. Then call the ComputeHash method and pass either a byte array or a System.IO.Stream object. The keyed hashing algorithm will process the input data and return a byte array containing the keyed hash code. Table 11-5 shows the size of hash code generated by each keyed hashing algorithm.

The Code

The following example demonstrates the generation of a keyed hash code from a file. The example uses the given class to generate the keyed hash code, and then displays it to the console. The example requires three command-line arguments: the name of the file from which the hash is calculated, the name of the class to instantiate, and the key to use when calculating the hash.

using System; using System.IO; using System.Text;

using System.Security.Cryptography;

C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

431

namespace Apress.VisualCSharpRecipes.Chapter11

{

class Recipe11_17

{

public static void Main(string[] args)

{

//Create a byte array from the key string, which is the

//second command-line argument.

byte[] key = Encoding.Unicode.GetBytes(args[2]);

//Create a KeyedHashAlgorithm derived object to generate the keyed

//hash code for the input file. Pass the byte array representing the

//key to the constructor.

using (KeyedHashAlgorithm hashAlg = KeyedHashAlgorithm.Create(args[1]))

{

//Assign the key. hashAlg.Key = key;

//Open a FileStream to read the input file. The filename is

//specified by the first command-line argument.

using (Stream file =

new FileStream(args[0], FileMode.Open, FileAccess.Read))

{

//Generate the keyed hash code of the file's contents. byte[] hash = hashAlg.ComputeHash(file);

//Display the keyed hash code to the console. Console.WriteLine(BitConverter.ToString(hash));

}

}

// Wait to continue.

Console.WriteLine("\nMain method complete. Press Enter."); Console.ReadLine();

}

}

}

Usage

Executing the following command:

Recipe11-17 Recipe11-17.exe HMACSHA1 secretKey

will display the following hash code to the console:

2E-5B-9B-2C-91-42-BA-4E-98-DF-39-F6-AE-89-B6-44-61-FB-32-E7

In contrast, executing this command:

Recipe11-17 Recipe11-17.exe HMACSHA1 anotherKey

will display the following hash code to the console:

EF-64-79-3A-3C-A4-44-01-AD-9E-94-2A-B4-58-CF-42-84-3E-27-91

432 C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

11-18. Work with Security-Sensitive Strings in Memory

Problem

You need to work with sensitive string data, such as passwords or credit card numbers, in memory and need to minimize the risk of other people or processes accessing that data.

Solution

Use the class System.Security.SecureString to hold the sensitive data values in memory.

How It Works

Storing sensitive data such as passwords, personal details, and banking information in memory as String objects is insecure for many reasons, including the following:

String objects are not encrypted.

The immutability of String objects means that whenever you change the String, the old String value is left in memory until it is garbage-collected and later overwritten.

Because the garbage collector is free to reorganize the contents of the managed heap, multiple copies of your sensitive data may be present on the heap.

If part of your process address space is swapped to disk or a memory dump is written to disk, a copy of your data may be stored on the disk.

Each of these factors increases the opportunities for others to access your sensitive data. In

.NET Framework versions 1.0 and 1.1, one solution to these problems is to use byte arrays to hold an encrypted version of the sensitive data. You have much better control over a byte array than you do with a string; principally, you can wipe the array any time you like. The .NET Framework 2.0 introduces the SecureString class to simplify the task of working with sensitive string data in memory.

You create a SecureString as either initially empty or from a pointer to a character (char) array. Then you manipulate the contents of the SecureString one character at a time using the methods

AppendChar, InsertAt, RemoveAt, and SetAt. As you add characters to the SecureString, they are encrypted using the capabilities of the Data Protection API.

Note The SecureString class uses features of Data Protection API (DPAPI) and is available only on Windows 2000 SP3 and later operating system versions.

The SecureString class also provides a method named MakeReadOnly. As the name suggests, calling MakeReadOnly configures the SecureString to no longer allow its value to be changed. Attempting to modify a SecureString marked as read-only results in the exception System.

InvalidOperationException being thrown. Once you have set the SecureString to read-only, it cannot be undone.

The SecureString class has a ToString method, but this does not retrieve a string representation of the contained data. Instead, the class System.Runtime.InteropServices.Marshal implements a number of static methods that take a SecureString object; decrypts it; converts it to a binary

C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

433

string, a block of ANSI, or a block of Unicode data; and returns a System.IntPtr object that points to the converted data.

At any time, you can call the SecureString.Clear method to clear the sensitive data, and when you have finished with the SecureString object, call its Dispose method to clear the data and free the memory. SecureString implements System.IDisposable.

Note Although it might seem that the benefits of the SecureString class are limited, because there is no way in Windows Forms applications to get such a secured string from the GUI without first retrieving a nonsecured String through a TextBox or another control, it is likely that third parties and future additions to the .NET Framework will use the SecureString class to handle sensitive data. This is already the case in System.Diagnostics. ProcessStartInfo, where using a SecureString, you can set the Password property to the password of the user context in which the new process should be run.

The Code

The following example reads a username and password from the console and starts Notepad.exe as the specified user. The password is masked on input and stored in a SecureString in memory, maximizing the chances of the password remaining secret.

using System;

using System.Security; using System.Diagnostics;

namespace Apress.VisualCSharpRecipes.Chapter11

{

class Recipe11_18

{

public static SecureString ReadString()

{

//Create a new emtpty SecureString. SecureString str = new SecureString();

//Read the string from the console one

//character at a time without displaying it. ConsoleKeyInfo nextChar = Console.ReadKey(true);

//Read characters until Enter is pressed. while (nextChar.Key != ConsoleKey.Enter)

{

if (nextChar.Key == ConsoleKey.Backspace)

{

if (str.Length > 0)

{

//Backspace pressed, remove the last character. str.RemoveAt(str.Length - 1);

Console.Write(nextChar.KeyChar); Console.Write(" "); Console.Write(nextChar.KeyChar);

}

else

{

Console.Beep();

}

434 C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

}

else

{

//Append the character to the SecureString and

//display a masked character. str.AppendChar(nextChar.KeyChar); Console.Write("*");

}

// Read the next character. nextChar = Console.ReadKey(true);

}

// String entry finished. Make it read-only. str.MakeReadOnly();

return str;

}

public static void Main()

{

string user = "";

//Get the username under which Notepad.exe will be run. Console.Write("Enter the user name: ");

user = Console.ReadLine();

//Get the user's password as a SecureString. Console.Write("Enter the user's password: "); using (SecureString pword = ReadString())

{

//Start Notepad as the specified user. ProcessStartInfo startInfo = new ProcessStartInfo();

startInfo.FileName = "notepad.exe"; startInfo.UserName = user; startInfo.Password = pword; startInfo.UseShellExecute = false;

// Create a new Process object.

using (Process process = new Process())

{

// Assign the ProcessStartInfo to the Process object. process.StartInfo = startInfo;

try

{

// Start the new process. process.Start();

}

catch (Exception ex)

{

Console.WriteLine("\n\nCould not start Notepad process."); Console.WriteLine(ex);

}

}

}

C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

435

// Wait to continue.

Console.WriteLine("\n\nMain method complete. Press Enter."); Console.ReadLine();

}

}

}

11-19. Encrypt and Decrypt Data Using the Data

Protection API

Problem

You need a convenient way to securely encrypt data without the headache associated with key management.

Solution

Use the ProtectedData and ProtectedMemory classes of the System.Security.Cryptography namespace in .NET Framework 2.0 to access the encryption and key management capabilities provided by the Data Protection API (DPAPI).

How It Works

Given that the .NET Framework provides you with well-tested implementations of the most widely used and trusted encryption algorithms, the biggest challenge you face when using cryptography is key management, namely the effective generation, storage, and sharing of keys to facilitate the use of cryptography. In fact, key management is the biggest problem facing most people when they want to securely store or transmit data using cryptographic techniques. If implemented incorrectly, key management can easily render useless all of your efforts to encrypt your data.

DPAPI provides encryption and decryption services without the need for you to worry about key management. DPAPI automatically generates keys based on Windows user credentials, stores keys securely as part of your profile, and even provides automated key expiry without losing access to previously encrypted data.

Note DPAPI is suitable for many common uses of cryptography in Windows applications, but will not help you in situations that require you to distribute or share secret or public keys with other users.

In versions 1.0 and 1.1 of the .NET Framework, you needed to use P/Invoke to work with DPAPI.

.NET Framework 2.0 introduces in System.Security.dll two managed classes that provide easy access to the encryption and decryption capabilities of DPAPI: ProtectedData and ProtectedMemory. Both classes allow you to encrypt a byte array by passing it to the static method Protect, and decrypt a byte array of encrypted data by passing it the static method Unprotect. The difference in the classes is in the scope that they allow you to specify when you encrypt and decrypt data.

Caution You must use ProtectedData if you intend to store encrypted data and reboot your machine before decrypting it. ProtectedMemory will be unable to decrypt data that was encrypted before a reboot.

436 C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

When you call ProtectedData.Protect, you specify a value from the enumeration System.Security. Cryptography.DataProtectionScope. The following are the possible values:

CurrentUser, which means that only code running in the context of the current user can decrypt the data

LocalMachine, which means that any code running on the same computer can decrypt the data

When you call ProtectedMemory.Protect, you specify a value from the enumeration

System.Security.Cryptography.MemoryProtectionScope. The possible values are as follows:

CrossProcess, which means that any code in any process can decrypt the encrypted data

SameLogon, which means that only code running in the same user context can decrypt the data

SameProcess, which means that only code running in the same process can decrypt the data

Both classes allow you to specify additional data (entropy) when you encrypt your data. Entropy makes certain types of cryptographic attacks less likely to succeed. If you choose to use entropy when you protect data, you must use the same entropy value when you unprotect the data. It is not essential that you keep the entropy data secret, so it can be stored freely without encryption.

The Code

The following example demonstrates the use of the ProtectedData class to encrypt a string entered at the console by the user. Note that you need to reference the System.Security.dll assembly.

using System; using System.Text;

using System.Security.Cryptography;

namespace Apress.VisualCSharpRecipes.Chapter11

{

class Recipe11_19

{

public static void Main()

{

//Read the string from the console. Console.Write("Enter the string to encrypt: "); string str = Console.ReadLine();

//Create a byte array of entropy to use in the encryption process. byte[] entropy = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };

//Encrypt the entered string after converting it to

//a byte array. Use CurrentUser scope so that only

//the current user can decrypt the data.

byte[] enc = ProtectedData.Protect(Encoding.Unicode.GetBytes(str), entropy, DataProtectionScope.LocalMachine);

//Display the encrypted data to the console. Console.WriteLine("\nEncrypted string = {0}",

BitConverter.ToString(enc));

//Attempt to decrypt the data using CurrentUser scope. byte[] dec = ProtectedData.Unprotect(enc,

entropy, DataProtectionScope.CurrentUser);

C H A P T E R 1 1 S E C U R I T Y A N D C RY P TO G R A P H Y

437

//Display the data decrypted using CurrentUser scope. Console.WriteLine("\nDecrypted data using CurrentUser scope = {0}",

Encoding.Unicode.GetString(dec));

//Wait to continue.

Console.WriteLine("\nMain method complete. Press Enter."); Console.ReadLine();

}

}

}

Соседние файлы в предмете Программирование на C++