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

Visual CSharp 2005 Recipes (2006) [eng]

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

418 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-12. Impersonate a Windows User

Problem

You need your code to run in the context of a Windows user other than the currently active user account.

Solution

Obtain a System.Security.Principal.WindowsIdentity object representing the Windows user you need to impersonate, and then call the Impersonate method of the WindowsIdentity object.

How It Works

Every Windows thread has an associated access token, which represents the Windows account on whose behalf the thread is running. The Windows operating system uses the access token to determine whether a thread has the appropriate permissions to perform protected operations on behalf of the account, such as read and write files, reboot the system, and change the system time.

By default, a managed application runs in the context of the Windows account that executed the application. This is normally desirable behavior, but sometimes you will want to run an application in the context of a different Windows account. This is particularly true in the case of server-side applications that process transactions on behalf of the users remotely connected to the server.

It’s common for a server application to run in the context of a Windows account created specifically for the application—a service account. This service account will have minimal permissions to access system resources. Enabling the application to operate as though it were the connected user permits the application to access the operations and resources appropriate to that user’s security clearance. When an application assumes the identity of another user, it’s known as impersonation. Correctly implemented, impersonation simplifies security administration and application design, while maintaining user accountability.

Note As discussed in recipe 11-11, a thread’s Windows access token and its .NET principal are separate entities and can represent different users. The impersonation technique described in this recipe changes only the Windows access token of the current thread; it does not change the thread’s principal. To change the thread’s principal, code must have the ControlPrincipal element of SecurityPermission and assign a new System.Security.

Principal.IPrincipal object to the CurrentPrincipal property of the current System.Threading.Thread.

The System.Security.Principal.WindowsIdentity class provides the functionality through which you invoke impersonation. However, the exact process depends on which version of Windows your application is running. If it’s running on Windows Server 2003 or later, the WindowsIdentity class supports constructor overloads that create WindowsIdentity objects based on the account name of the user you want to impersonate. On all previous versions of Windows, you must first obtain

a System.IntPtr containing a reference to a Windows access token that represents the user to impersonate. To obtain the access token reference, you must use a native method such as the LogonUser function from the Win32 API.

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

419

Caution A major issue with performing impersonation on Microsoft Windows 2000 and Windows NT is that an account must have the Windows privilege SE_TCB_NAME to execute LogonUser. This requires you to configure Windows security policy and grant the account the right to “act as part of operating system.” This grants the account a very high level of trust. You should never grant the privilege SE_TCB_NAME directly to user accounts. The requirement for an account to have the SE_TCB_NAME privilege no longer exists for Windows 2003 and Windows XP.

Once you have a WindowsIdentity object representing the user you want to impersonate, call its Impersonate method. From that point on, all actions your code performs occur in the context of the impersonated Windows account. The Impersonate method returns a System.Security.Principal.

WindowsSecurityContext object, which represents the active account prior to impersonation. To revert to the original account, call the Undo method of this WindowsSecurityContext object.

The Code

The following example demonstrates impersonation of a Windows user. The example uses the LogonUser function of the Win32 API to obtain a Windows access token for the specified user, impersonates the user, and then reverts to the original user context.

using System; using System.IO;

using System.Security.Principal; using System.Security.Permissions; using System.Runtime.InteropServices;

//Ensure the assembly has permission to execute unmanaged code

//and control the thread principal. [assembly:SecurityPermission(SecurityAction.RequestMinimum,

UnmanagedCode=true, ControlPrincipal=true)]

namespace Apress.VisualCSharpRecipes.Chapter11

{

class Recipe11_12

{

//Define some constants for use with the LogonUser function. const int LOGON32_PROVIDER_DEFAULT = 0;

const int LOGON32_LOGON_INTERACTIVE = 2;

//Import the Win32 LogonUser function from advapi32.dll. Specify

//"SetLastError = true" to correctly support access to Win32 error

//codes.

[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] static extern bool LogonUser(string userName, string domain,

string password, int logonType, int logonProvider, ref IntPtr accessToken);

public static void Main(string[] args)

{

//Create a new IntPtr to hold the access token returned by the

//LogonUser function.

IntPtr accessToken = IntPtr.Zero;

//Call LogonUser to obtain an access token for the specified user.

//The accessToken variable is passed to LogonUser by reference and

420C 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

//will contain a reference to the Windows access token if

//LogonUser is successful.

bool success = LogonUser(

 

args[0],

// username to log on.

".",

// use the local account database.

args[1],

// user's password.

LOGON32_LOGON_INTERACTIVE,

// create an interactive login.

LOGON32_PROVIDER_DEFAULT,

// use the default logon provider.

ref accessToken

// receives access token handle.

);

 

//If the LogonUser return code is zero, an error has occurred.

//Display the error and exit.

if (!success)

{

Console.WriteLine("LogonUser returned error {0}", Marshal.GetLastWin32Error());

}

else

{

//Create a new WindowsIdentity from the Windows access token. WindowsIdentity identity = new WindowsIdentity(accessToken);

//Display the active identity.

Console.WriteLine("Identity before impersonation = {0}",

WindowsIdentity.GetCurrent().Name);

//Impersonate the specified user, saving a reference to the

//returned WindowsImpersonationContext, which contains the

//information necessary to revert to the original user

//context.

WindowsImpersonationContext impContext = identity.Impersonate();

//Display the active identity. Console.WriteLine("Identity during impersonation = {0}",

WindowsIdentity.GetCurrent().Name);

//*****************************************

//Perform actions as the impersonated user.

//*****************************************

//Revert to the original Windows user using the

//WindowsImpersonationContext object. impContext.Undo();

//Display the active identity. Console.WriteLine("Identity after impersonation = {0}",

WindowsIdentity.GetCurrent().Name);

//Wait to continue.

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

}

}

}

}

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

421

Usage

The example expects two command-line arguments: the account name of the user on the local machine to impersonate and the account’s password. For example, the command Recipe11-12 Bob password impersonates the user Bob, as long as that user exists in the local accounts database and his password is “password.”

11-13. Create a Cryptographically Random Number

Problem

You need to create a random number that is suitable for use in cryptographic and security applications.

Solution

Use a cryptographic random number generator such as the System.Security.Cryptography. RNGCryptoServiceProvider class.

How It Works

The System.Random class is a pseudo-random number generator that uses a mathematical algorithm to simulate the generation of random numbers. In fact, the algorithm it uses is deterministic, meaning that you can always calculate what the next number will be based on the previously generated number. This means that numbers generated by the Random class are unsuitable for use in situations in which security is a priority, such as generating encryption keys and passwords.

When you need a nondeterministic random number for use in cryptographic or security-related applications, you must use a random number generator derived from the class System.Security. Cryptography.RandomNumberGenerator. The RandomNumberGenerator class is an abstract class from which all concrete .NET random number generator classes should inherit. Currently, the RNGCryptoServiceProvider class is the only concrete implementation provided. The RNGCryptoServiceProvider class provides a managed wrapper around the CryptGenRandom function of the Win32 CryptoAPI, and you can use it to fill byte arrays with cryptographically random byte values.

Note The numbers produced by the RNGCryptoServiceProvider class are not truly random. However, they are sufficiently random to meet the requirements of cryptography and security applications in most commercial and government environments.

As is the case with many of the .NET cryptography classes, the RandomNumberGenerator base class is a factory for the concrete implementation classes that derive from it. Calling RandomNumberGenerator. Create("System.Security.Cryptography.RNGCryptoServiceProvider") will return an instance of

RNGCryptoServiceProvider that you can use to generate random numbers. In addition, because RNGCryptoServiceProvider is the only concrete implementation provided, it’s the default class created if you call the Create method without arguments, as in RandomNumberGenerator.Create().

Once you have a RandomNumberGenerator instance, the method GetBytes fills a byte array with random byte values. As an alternative, you can use the GetNonZeroBytes method if you need random data that contains no zero values.

422 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

The Code

The following example instantiates an RNGCryptoServiceProvider object and uses it to generate random values.

using System;

using System.Security.Cryptography;

namespace Apress.VisualCSharpRecipes.Chapter11

{

class Recipe11_13

{

public static void Main() {

//Create a byte array to hold the random data. byte[] number = new byte[32];

//Instantiate the default random number generator. RandomNumberGenerator rng = RandomNumberGenerator.Create();

//Generate 32 bytes of random data.

rng.GetBytes(number);

//Display the random number. Console.WriteLine(BitConverter.ToString(number));

//Wait to continue.

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

}

}

}

Note The computational effort required to generate a random number with RNGCryptoServiceProvider is significantly greater than that required by Random. For everyday purposes, the use of RNGCryptoServiceProvider is overkill. You should consider the quantity of random numbers you need to generate and the purpose of the numbers before deciding to use RNGCryptoServiceProvider. Excessive and unnecessary use of the RNGCryptoServiceProvider class could have a noticeable effect on application performance if many random numbers are generated.

11-14. Calculate the Hash Code of a Password

Problem

You need to store a user’s password securely so that you can use it to authenticate the user in the future.

Solution

Create and store a cryptographic hash code of the password using a hashing algorithm class derived from the System.Security.Cryptography.HashAlgorithm class. On future authentication attempts, generate the hash of the password entered by the user and compare it to the stored hash code.

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

423

Caution You should never store a user’s plaintext password, because it is a major security risk and one that most users would not appreciate, given that many of them will use the same password to access multiple systems.

How It Works

Hashing algorithms are one-way cryptographic functions that take plaintext of variable length and generate a fixed-size numeric value. They are one-way because it’s nearly impossible to derive the original plaintext from the hash code. Hashing algorithms are deterministic; applying the same hashing algorithm to a specific piece of plaintext always generates the same hash code. This makes hash codes useful for determining if two blocks of plaintext (passwords in this case) are the same. The design of hashing algorithms ensures that the chance of two different pieces of plaintext generating the same hash code is extremely small (although not impossible). In addition, there is no correlation between the similarity of two pieces of plaintext and their hash codes; minor differences in the plaintext cause significant differences in the resulting hash codes.

When using passwords to authenticate a user, you are not concerned with the content of the password that the user enters. You need to know only that the entered password matches the password that you have recorded for that user in your accounts database.

The nature of hashing algorithms makes them ideal for storing passwords securely. When the user provides a new password, you must create the hash code of the password and store it, and then discard the plaintext password. Each time the user tries to authenticate with your application, calculate the hash code of the password that user provides and compare it with the hash code you have stored.

Note People regularly ask how to obtain a password from a hash code. The simple answer is that you cannot. The whole purpose of a hash code is to act as a token that you can freely store without creating security holes. If a user forgets a password, you cannot derive it from the stored hash code. Rather, you must either reset the account to some default value or generate a new password for the user.

Generating hash codes is simple in the .NET Framework. The abstract class HashAlgorithm provides a base from which all concrete hashing algorithm implementations derive. The .NET Framework class library includes the seven hashing algorithm implementations listed in Table 11-4; each implementation class is a member of the System.Security.Cryptography namespace. The classes with names ending in CryptoServiceProvider wrap functionality provided by the native Win32 CryptoAPI, whereas those with names ending in Managed are fully implemented in managed code.

Table 11-4. Hashing Algorithm Implementations

Algorithm Name

Class Name

Hash Code Size (in Bits)

MD5

MD5CryptoServiceProvider

128

RIPEMD160 or

RIPEMD160Managed

160

RIPEMD-160

 

 

(new in .NET 2.0)

 

 

SHA or SHA1

SHA1CryptoServiceProvider

160

SHA1Managed

SHA1Managed

160

SHA256 or SHA-256

SHA256Managed

256

SHA384 or SHA-384

SHA384Managed

384

SHA512 or SHA-512

SHA512Managed

512

 

 

 

424 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

Although you can create instances of the hashing algorithm classes directly, the HashAlgorithm base class is a factory for the concrete implementation classes that derive from it. Calling the static method HashAlgorithm.Create will return an object of the specified type. Using the factory approach allows you to write generic code that can work with any hashing algorithm implementation. Note that unlike in recipe 11-13, you do not pass the class name as parameter to the factory; instead, you pass the algorithm name.

Once you have a HashAlgorithm object, its ComputeHash method accepts a byte array argument containing plaintext and returns a new byte array containing the generated hash code. Table 11-4 shows the size of hash code (in bits) generated by each hashing algorithm class.

Note The SHA1Managed algorithm cannot be implemented using the factory approach. It must be instantiated directly.

The Code

The example shown here demonstrates the creation of a hash code from a string, such as a password. The application expects two command-line arguments: the name of the hashing algorithm to use and the string from which to generate the hash. Because the HashAlgorithm.ComputeHash method requires a byte array, you must first byte-encode the input string using the class System.Text.Encoding, which provides mechanisms for converting strings to and from various character-encoding formats.

using System; using System.Text;

using System.Security.Cryptography;

namespace Apress.VisualCSharpRecipes.Chapter11

{

class Recipe11_14

{

public static void Main(string[] args)

{

//Create a HashAlgorithm of the type specified by the first

//command-line argument.

HashAlgorithm hashAlg = null;

if (args[0].CompareTo("SHA1Managed") == 0)

{

hashAlg = new SHA1Managed();

}

else

{

hashAlg = HashAlgorithm.Create(args[0]);

}

using (hashAlg)

{

//Convert the password string, provided as the second

//command-line argument, to an array of bytes. byte[] pwordData = Encoding.Default.GetBytes(args[1]);

//Generate the hash code of the password.

byte[] hash = hashAlg.ComputeHash(pwordData);

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

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

425

// Wait to continue.

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

}

}

}

}

Usage

Running the following command:

Recipe11-14 SHA1 ThisIsMyPassword

will display the following hash code to the console:

30-B8-BD-58-29-88-89-00-D1-5D-2B-BE-62-70-D9-BC-65-B0-70-2F

In contrast, executing this command:

Recipe11-14 RIPEMD-160 ThisIsMyPassword

will display the following hash code:

0C-39-3B-2E-8A-4E-D3-DD-FB-E3-C8-05-E4-62-6F-6B-76-7C-7A-49

11-15. Calculate the Hash Code of a File

Problem

You need to determine if the contents of a file have changed over time.

Solution

Create a cryptographic hash code of the file’s contents using the ComputeHash method of the System. Security.Cryptography.HashAlgorithm class. Store the hash code for future comparison against newly generated hash codes.

How It Works

As well as allowing you to store passwords securely (discussed in recipe 11-14), hash codes provide an excellent means of determining if a file has changed. By calculating and storing the cryptographic hash of a file, you can later recalculate the hash of the file to determine if the file has changed in the interim. A hashing algorithm will produce a very different hash code even if the file has been changed only slightly, and the chances of two different files resulting in the same hash code are extremely small.

Caution Standard hash codes are not suitable for sending with a file to ensure the integrity of the file’s contents. If someone intercepts the file in transit, that person can easily change the file and recalculate the hash code, leaving the recipient none the wiser. Recipe 11-17 discusses a variant of the hash code—a keyed hash code—that is suitable for ensuring the integrity of a file in transit.

426 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

The HashAlgorithm class makes it easy to generate the hash code of a file. First, instantiate one of the concrete hashing algorithm implementations derived from the HashAlgorithm class. To instantiate the desired hashing algorithm class, pass the name of the hashing algorithm to the HashAlgorithm.Create method, as described in recipe 11-14. See Table 11-4 for a list of valid hashing algorithm names. Then, instead of passing a byte array to the ComputeHash method, you pass a System.IO.Stream object representing the file from which you want to generate the hash code. The HashAlgorithm object handles the process of reading data from the Stream and returns a byte array containing the hash code for the file.

The Code

The example shown here demonstrates the generation of a hash code from a file. The application expects two command-line arguments: the name of the hashing algorithm to use and the name of the file from which the hash is calculated.

using System; using System.IO;

using System.Security.Cryptography;

namespace Apress.VisualCSharpRecipes.Chapter11

{

class Recipe11_15

{

public static void Main(string[] args)

{

//Create a HashAlgorithm of the type specified by the first

//command-line argument.

using (HashAlgorithm hashAlg = HashAlgorithm.Create(args[0]))

{

//Open a FileStream to the file specified by the second

//command-line argument.

using (Stream file =

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

{

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

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

}

// Wait to continue.

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

}

}

}

}

Usage

Running this command:

Recipe11-15 SHA1 Recipe11-15.exe

will display the following hash code to the console:

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

427

CA-67-A5-2D-EC-E9-FC-45-AE-97-E9-E1-38-CB-17-86-BB-17-EE-30

In contrast, executing this command:

Recipe11-15 RIPEMD-160 Recipe11-15.exe

will display the following hash code:

E1-6E-FA-BB-89-BA-DA-83-20-D5-CA-EC-FC-3D-52-13-86-B9-41-7C

11-16. Verify a Hash Code

Problem

You need to verify a password or confirm that a file remains unchanged by comparing two hash codes.

Solution

Convert both the old and the new hash codes to hexadecimal code strings, Base64 strings, or byte arrays and compare them.

How It Works

You can use hash codes to determine if two pieces of data (such as passwords or files) are the same, without the need to store, or even maintain access to, the original data. To determine if data changes over time, you must generate and store the original data’s hash code. Later, you can generate another hash code for the data and compare the old and new hash codes, which will show if any change has occurred. The format in which you store the original hash code will determine the most appropriate way to verify a newly generated hash code against the stored one.

Note The recipes in this chapter use the ToString method of the class System.BitConverter to convert byte arrays to hexadecimal string values for display. Although easy to use and appropriate for display purposes, you might find this approach inappropriate for use when storing hash codes, because it places a hyphen (-) between each byte value (for example, 4D-79-3A-C9-. . .). In addition, the BitConverter class does not provide a method to parse such a string representation back into a byte array.

Hash codes are often stored in text files, either as hexadecimal strings (for example,

89D22213170A9CFF09A392F00E2C6C4EDC1B0EF9), or as Base64-encoded strings (for example, idIiExcKnP8Jo5LwDixsTtwbDvk=). Alternatively, hash codes may be stored in databases as raw byte values. Regardless of how you store your hash code, the first step in comparing old and new hash codes is to get them both into a common form.

The Code

This following example contains three methods that use different approaches to compare hash codes:

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