Working with CryptoAPI 1.0

for RuBoard

Microsoft's CryptoAPI consists roughly of two related sets of APIs ”a low-level set of functions (CryptoAPI 1.0) that perform core cryptographic operations, such as basic encryption and digital signature operations, and a higher-level set of functions (CryptoAPI 2.0) designed to work with X.509v3/PKIX Part 1 digital certificates and S/MIME (CMS) signed messages. This section discusses interoperating with CryptoAPI 1.0 functions and data structures from the .NET Framework; CryptoAPI 2.0 functions are addressed in the next section.

The CryptoAPI Provider Model: Cryptographic Service Providers and Key Containers

CryptoAPI 1.0 contains a pluggable-provider model that permits third parties to implement cryptographic algorithms and "plug" them into the CryptoAPI architecture. At a high level, CryptoAPI's extensibility model is similar to that of the .NET Framework cryptographic object model; both architectures allow developers to write their own implementations of algorithms and make them available to other developers through common abstractions. CryptoAPI's unit of extension is called a cryptographic service provider (CSP); a CSP consists of implementations of one or more cryptographic algorithms. Developers using CryptoAPI can ask for a specific CSP when they begin performing cryptographic operations, or they can use the "default" CSP on the system for the particular algorithm that they want to use.

Cryptographic service providers are organized within CryptoAPI by Types; each Type of CSP defines a collection of algorithms that are implemented by all CSPs of that Type. (CSP Types are integer values defined by Microsoft within the wincrypt.h header file in the Win32 Platform SDK.) The most common provider type is Type 1, the PROV_RSA_FULL provider, that implements the RSA asymmetric algorithm and the DES, TripleDES, and RC2 symmetric algorithms. Table 31.1 lists the commonly used CryptoAPI provider Types. Typically, if you are interested in performing RSA operations, you will want to use a Type 1 CSP; if you are performing DSA operations, you will want to use a Type 13 CSP. Types tend to align closely with asymmetric algorithms, so, for example, both Type 1 and Type 13 CSPs include implementations of the MD5 and SHA1 hash algorithms, but you cannot use a Type 1 CSP to create or verify a DSA signature.

Table 31.1. CryptoAPI Provider Types
Provider Type ( wincrypt.h Symbol) Provider Type Numeric Value Description
PROV_RSA_FULL 1 RSA implementations
PROV_DSS 3 DSA implementations
PROV_RSA_SCHANNEL 12 An RSA-based CSP with additional key derivation algorithms required by the SSL/TLS standard
PROV_DSS_DH 13 DSA/Diffie-Hellman implementations
PROV_DH_SCHANNEL 18 A DSA/Diffie-Hellman CSP with additional key derivation algorithms required by the SSL/TLS standard
PROV_RSA_AES 24 An expanded version of the PROV_RSA_FULL provider with additional support for the AES/Rijndael symmetric block cipher

Within a particular Type, individual CSPs are identified by a name that is simply an implementation-defined string. The default CSPs that ship with Windows XP for each Type are shown in Table 31.2. CSPs of the same Type implement the same algorithms (or, at least, are supposed to implement the same algorithms), but will implement those algorithms in different ways and perhaps using different resources (for example, hardware accelerators).

Table 31.2. Windows XP Default Cryptographic Service Providers
Provider Type Numeric Value Windows XP Default CSP
1 Microsoft Strong Cryptographic Provider
3 Microsoft Base DSS Cryptographic Provider
12 Microsoft RSA SChannel Cryptographic Provider
13 Microsoft Enhanced DSS and Diffie-Hellman Cryptographic Provider
18 Microsoft DH SChannel Cryptographic Provider
24 Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)

NOTE

Prior to Windows XP, the default Type 1 CSP depended on whether the platform had received a "strong cryptography" update. The Microsoft Base Cryptographic Provider was designed to comply with the United States export controls present prior to the release of Windows 2000 and was the default CSP on all platforms. The "strong cryptography" updates available from Microsoft replaced the Base CSP with the Microsoft Enhanced Cryptographic Provider, which supports larger key lengths. After the United States reduced the export restrictions on cryptography, Microsoft released the Strong Cryptographic Provider, which is backward-compatible with both the Base and Enhanced providers. Thus, depending on the particular platform on which you are operating, one or more of the Base, Enhanced, and Strong providers may be present. When searching for a Microsoft Type 1 provider to use, use the Strong provider if available; if the Strong provider is not available, see if the Enhanced provider exists. Platforms that only have the Base provider installed should be upgraded with the "strong cryptography" update.


Within each CSP are one or more cryptographic key pairs, stored in individual key containers. A key container is a named key pair; each CSP manages its own keys, including how those keys are persisted and the mapping between keys and key containers. The number of key containers that can be filled within a CSP at any given point in time is a property of the CSP implementation (typically, the limiting factor is key storage space, such as on a hardware token or smartcard ). Thus, to access a specific key pair in CryptoAPI, you need to know the Type of the CSP it is stored within, the name of that CSP, and the name of the key container within the CSP that holds the key.

CAUTION

A word of caution about cryptographic keys generated by the .NET Framework: Within CryptoAPI, the private key component of a key pair may be marked either exportable or non-exportable. Exportable private keys can be extracted from the cryptographic service provider and moved around (such as to another CSP or copied to a file). Non-exportable private keys cannot be extracted. An example showing how to create a non-exportable private key is provided later in this section.


Accessing Specific Providers and Key Containers from the .NET Framework Using the CspParameters Structure

In general, the .NET Framework classes that use CryptoAPI ”the classes with names ending in CryptoServiceProvider ”do a good job of hiding the details of CryptoAPI's own architecture from the developer. Constructors for classes such as RSACryptoServiceProvider and DSACryptoServiceProvider automatically use the default CSPs on the machine when they need to perform RSA or DSA operations (the default Type 1 and Type 13 providers, respectively). To use a particular CSP or key container within a CSP with these classes, we simply need to identify the CSP or key container when constructing a new instance of the class. We can specify this data by including a CspParameters structure in the calls to the RSACryptoServiceProvider , DSACryptoServiceProvider , and RNGCryptoServiceProvider constructors.

The CspParameters structure is our method of communicating to CryptoAPI from managed code information identifying the CSP and key container we want to use for a cryptographic operation. The RSACryptoServiceProvider , DSACryptoServiceProvider , and RNGCryptoServiceProvider classes each have one or more constructor overloads that accept a CspParameters object as an argument to identify what specific CSP and/or key container holds the key we want to use. An instance of the CspParameters class contains four public fields and a single public property, as summarized in Table 31.3. The ProviderType and ProviderName fields specify which Cryptographic Service Provider to use for cryptographic operations and key storage. The KeyContainerName and KeyNumber fields, together with the value of the Flags property, identify the key storage location to use within the Cryptographic Service Provider.

NOTE

The KeyContainerName and KeyNumber fields and the Flags property within a CspParameters object are relevant only to the RSACryptoServiceProvider and DSACryptoServiceProvider classes, because there are no keys (and thus no key containers) used when requesting random numbers from an instance of the RNGCryptoServiceProvider class. It is possible to direct the RNGCryptoServiceProvider class to use another CSP, so the constructors on that class do use the information in the ProviderType and ProviderName fields. CspParameter information is not used with the other CSP-related classes in the .NET Framework ( MD5CryptoServiceProvider , SHA1CryptoServiceProvider , DESCryptoServiceProvider , TripleDESCryptoServiceProvider and RC2CryptoServiceProvider ). (CryptoAPI does not allow symmetric keys to be persisted directly in key containers, so there is no way to point an instance of, for example, TripleDESCryptoServiceProvider , to use a specific key that is already loaded into CryptoAPI.)


Table 31.3. Fields and Properties of the CspParameters Structure
Field/Property Name Contents Default Value
ProviderType Cryptographic Service Provider Type (integer) 1
ProviderName Cryptographic Service Provider Name (string) Null
KeyContainerName Key Container Name (string) Null
KeyNumber AT_KEYEXHCANGE or AT_SIGNATURE Varies by class
Flags CspProviderFlags

To use a specific cryptographic service provider or key container, simply create a corresponding CspParameters object and pass it as an argument to the RSACryptoServiceProvider , DSACryptoServiceProvider , or RNGCryptoServiceProvider constructor.

NOTE

Depending on the configuration of the operating system on which the CLR is running, some CryptoAPI-based cryptographic operations, CSPs, or key containers may not be available to your program. If you specify invalid values in the CspParameters object (for example, the name of a CSP that is not installed on the platform), one or more CryptographicExceptions may be thrown. It is especially important to program defensively, catching errors and handling them gracefully, when using CspParameters to access specific CSPs or key containers because it may not be possible for CryptoAPI to satisfy your request.


Let's now look at some practical examples of using the CspParameters object to access specific CSPs and key containers. Consider the following statement:

 RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); 

This line of code creates a new RSACryptoServiceProvider object and assigns it to the rsa variable. Because no CspParameters object was specified in the constructor, the RSACryptoServiceProvider class uses the default Type 1 CSP installed in Windows and a randomly generated key container name. This is exactly equivalent to the following code snippet that makes the use of an explicit CspParameters object:

 CspParameters cspParams = new CspParameters(); RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspParams); 

By default, when you create a CspParameters object using its null constructor, it indicates that you want to use a random, temporary key container in the default Type 1 CSP. Thus, the following two statements are equivalent:

 CspParameters cspParams = new CspParameters(); CspParameters cspParams = new CspParameters(1); 

The second argument to the CspParameters constructor, if present, is a String containing the name of the cryptographic service provider to use. A value of null indicates that the default provider on the platform for the specified provider type should be used. The default cryptographic service provider for a given provider type varies, depending on the specific version of the Windows operating system that is installed on the machine. For versions of Windows earlier than Windows XP, the default provider may also depend on whether a "strong cryptography" update has been installed. On Windows XP, the default Type 1 provider is called the Microsoft Strong Cryptographic Provider , so the following statements are equivalent:

 CspParameters cspParams = new CspParameters(1); CspParameters cspParams = new CspParameters(1, null); CspParameters cspParams = new CspParameters(1, "Microsoft Strong Cryptographic Provider"); 

The third argument to the CspParameters constructor specifies the name of the key container to access within the cryptographic service provider. The key container name is also a String value. A value of null (the default value) indicates that the .NET Framework should generate a random key container name. If a non- null value is provided, the .NET Framework will look for a key container with that exact name. If present, the key within that container will be used. If the named key container does not already exist, a new key container with the specified name will be created and a random key will be generated within the key container.

The following code snippet shows how to use a key stored in a specific key container with the RSACryptoServiceProvider class. The name of the key container is "My RSA Key" , and we assume it is stored within the default Type 1 cryptographic service provider on the platform:

 CspParameters cspParams = new CspParameters(1, null, "My RSA Key"); RSACryptoServiceProvider rsa = new RSACryptoServiceprovider(cspParams); 

In most cases, you will not need to worry about the KeyNumber field within a CspParameters object (also the fourth argument to the CspParameters constructor), but we describe it here for completeness. Each CryptoAPI key container is actually capable of storing two pairs of keys, one pair for "key exchange" use and a second pair for "signature" use. Many production systems that use public key cryptography for both encryption and digital signatures issue a separate key pair for each function to each user of the system. (Separating encryption keys from signature keys allows the encryption key to be escrowed with a central authority ”in case data needs to be recovered ”but never duplicates the signature key to reduce the likelihood of signature repudiation .) By default, the RSACryptoServiceProvider class attempts to use the key pair in the "key exchange" slot of the key container when creating or acquiring keys. You can override this behavior by explicitly setting the value of the KeyNumber to indicate which slot within the key container you want to use. Table 31.4 shows the legal values for the KeyNumber field; these values correspond to constant values defined in the wincrypt.h header file for the Win32 CryptoAPI functions.

NOTE

While the RSACryptoServiceProvider class may be used with either "key exchange" or "signature" slot keys within a key container, the DSACryptoServiceProvider class will only operate with keys located in the "signature" slot of the container. This restriction is due to the fact that the DSA algorithm can only be used to create digital signatures, not perform general public key encryption operations. The DSACryptoServiceProvider class uses the "signature" slot of the specified key container by default, and attempting to use a "key exchange" slot key pair will generate a CryptographicException .

Unless you explicitly need to access the "signature" slot of a key container to use with RSACryptoServiceProvider , we strongly suggest leaving the KeyNumber field as its default value and letting the .NET Framework figure out the proper value to use when communicating with the underlying cryptographic service provider. The following is sample code for accessing the "signature" slot of the "My RSA Key" key container:

 CspParameters cspParams = new CspParameters(1, null, "My RSA Key"); cspParams.KeyNumber = 2; // 2 = AT_SIGNATURE RSACryptoServiceProvider rsa = new RSACryptoServiceprovider(cspParams); 

Table 31.4. Legal Values for the CspParameters KeyNumber Field
KeyNumber Value Key Container "Slot" Corresponding WinCrypt.h Constant
-1 Default for class (key exchange for RSACryptoServiceProvider , signature for DSACryptoServiceProvider ) N/A
1 "key exchange" ( RSACryptoServiceProvider only) AT_KEYEXCHANGE
2 "signature" ( RSACryptoServiceProvider or DSACryptoServiceProvider) AT_SIGNATURE
0x0000A400 "key exchange" ( RSACryptoServiceProvider only) CALG_RSA_KEYX
0x00002400 "signature" ( RSACryptoServiceProvider only) CALG_RSA_SIGN
0x00002200 "signature" ( DSACryptoServiceProvider only) CALG_DSS_SIGN

Key container selection is also influenced by the value of the Flags property on a CspParameters objects. The Flags property contains an enum of type CspProviderFlags ; there are two defined values for CspProviderFlags in version 1 of the .NET Framework ” UseMachineKeyStore and UseDefaultKeyContainer . Either or both of these flags can be specified in an instance of CspProviderFlags (they can be OR 'd together).

The UseMachineKeyStore flag is used to tell CryptoAPI to look for the key container in the set of per-machine keys instead of per-user keys. Within CryptoAPI, every logged-on user has access to an independent, isolated key storage database, and there is an additional database that holds "machine keys" that do not belong to any particular user. Typically, access to "machine key" storage is restricted to machine administrators. The UseMachineKeyStore flag is useful if you are performing cryptographic operations in a context in which there is no logged-on user account (and thus no per-user key storage associated with the process ID of the running process).

CryptoAPI also defines a "default" key container within each cryptographic service provider. The default key container is a persistent key container that is used by CryptoAPI when no explicit key container is requested . In the .NET Framework, you can access the default key container within a CSP by setting the UseDefaultKeyContainer flag on Flags property of the CspParameters object. When set, this flag instructs CryptoAPI to access the default key container. Like any other key container, if the default key container is empty, the .NET Framework will automatically generate a random key and insert it into the default container. The following example shows how to access the default key container when using the default Type 1 CSP with the RSACryptoServiceProvider class:

 CspParameters cspParams = new CspParameters(1); cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer; RSACryptoServiceProvider rsa = new RSACryptoServiceprovider(cspParams); 

It is possible to combine both the UseDefaultKeyContainer and UseMachineKeyStore flags if you want to access the default machine key container:

 CspParameters cspParams = new CspParameters(1); cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer  CspProviderFlags. graphics/ccc.gif UseMachineKeyStore; RSACryptoServiceProvider rsa = new RSACryptoServiceprovider(cspParams); 

NOTE

Within the .NET Framework, a key container is considered either transient or persistent. A transient key container (and the key material stored within it) is deleted when the .NET Framework object corresponding to the object is disposed or garbage collected. A persistent key container is not deleted when the corresponding managed object disappears; key material stored in a persistent key container will remain in the cryptographic service provider and can be accessed again in the future.

When the .NET Framework is asked to generate a random key container name, the corresponding key container is marked as a transient container. If an explicit key container name is specified in the CspParameters object passed to an RSACryptoServiceProvider or DSACryptoServiceProvider constructor, the corresponding key container is marked persistent. The state of a key container can be switched from transient to persistent by setting the value of the PersistKeyInCsp property to true .

Creating or accessing a persistent key container is a protected operation. Callers must have the right to call unmanaged code, SecurityPermission(SecurityPermissionFlag.UnmanagedCode) , whenever explicitly requesting a specific key container name or setting the value of the PersistKeyInCsp property. (Setting the value to true is the equivalent of requesting that a new key container be created within CryptoAPI. Setting PersistKeyInCsp to false causes the key container to be deleted when the related managed object is garbage collected. Both of these operations are equivalent to direct calls to CryptoAPI, so they are protected with the same permission demand.) Requesting the default key container within a CSP (setting the Flags property to an enum value including CspProviderFlags.UseDefaultKeyContainer ) is also a protected operation.


In addition to specifying where a cryptographic key pair is stored via a CspParameters object, you can also specify the size of the public key pair to be generated when filling a new key container. Two of the constructors on each of the RSACryptoServiceProvider and DSACryptoServiceProvider classes allow for explicit specification of the key size. They are

 RSACryptoServiceProvider(int size); RSACryptoServiceProvider(int size, CspParameters parameters); DSACryptoServiceProvider(int size); DSACryptoServiceProvider(int size, CspParameters parameters); 

The size parameter in these constructors specifies the desired size in bits of the public key modulus . For RSACryptoServiceProvider , the requested key size must be supported by the underlying cryptographic service provider. As specified in Table 30.5 of Chapter 30, the default key size for RSACryptographicServiceProvider is 1024 bits if supported by the platform, and 512 bits if not. (Prior to Windows XP, the "high encryption" update is required to support RSA encryption key sizes in excess of 512 bits.) The default key size when using DSACryptoServiceProvider is always 1024 bits (the maximum permitted by the algorithm). The following example generates a 2048-bit RSA key in a new key container ( "My bigger RSA key" ):

 CspParameters cspParams = new CspParameters(1,null,"My bigger RSA key"); RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048,cspParams); 

Calling CryptoAPI 1.0 Functions Directly Using Platform Invoke

The .NET Framework cryptography classes expose the most common CryptoAPI 1.0 functions cleanly through its object hierarchy. However, not all CryptoAPI functions or options are easily available through the managed classes, so sometimes you may need to call some CryptoAPI functions directly via the .NET Framework's platform invoke mechanism. The platform invoke feature allows managed code to call unmanaged functions that are exported from DLLs, such as the Win32 APIs.

To demonstrate the use of the .NET Framework's platform invoke feature, we will consider how to create a CryptoAPI key container that contains a non-exportable private key. Recall from earlier that the key pairs generated by the .NET Framework's RSACryptoServiceProvider and DSACryptoServiceProvider classes are exportable by default ”the private key components of the key pair may be extracted from the key container and distributed. When a new public/private key pair is created by CryptoAPI, the private components are marked with one of three states ”exportable, non-exportable, or user interface-protected (UI-protected). Non-exportable private keys can never be extracted from the cryptographic service provider that generated them. A UI-protected key can be exported from the cryptographic service provider, but attempting to use the private key or exporting it from the CSP will result in a dialog window being shown to the user requesting confirmation (and perhaps a password to unlock access to the key, depending on the type of UI protection desired by the user).

The exportability of a key, as well as whether confirmation from the user is required to use the key, is specified at the time the key is created by the CryptGenKey function within CryptoAPI. Because the .NET Framework cryptographic classes do not expose a mechanism for setting flags on its calls to CryptGenKey , we need to create the key in the proper key container ourselves . Creating the key will occur in two steps. First, we will need to acquire a handle on the desired key container in the cryptographic service provider where we want the key to live. Second, after we have a handle on the correct key container, we will create a new key in that container with the appropriate properties. After the key has been properly generated in its key container, we can access the container normally through the .NET Framework.

The CryptAcquireContext method is the function in CryptoAPI for acquiring a handle on a specific key container within a cryptographic service provider. The API definition for CryptAcquireContext is as follows :

 BOOL WINAPI CryptAcquireContext(   HCRYPTPROV*  phProv,  // handle to a key container (out parameter)   LPCTSTR  pszContainer,  // name of the key container to access   LPCTSTR  pszProvider,  // name of the cryptographic service provider to use   DWORD  dwProvType,  // type of cryptographic service provider to use   DWORD  dwFlags         // flags (e.g. create or delete container)  ); 

To access this API from managed code, we use the DllImport attribute (part of platform invoke) to define a managed entry point for the external function. The CryptAcquireContext function is defined in the advapi32.dll library DLL (part of Windows), so the declaration to access this method from managed code is as follows:

 [DllImport("advapi32.dll",EntryPoint="CryptAcquireContext", CharSet=CharSet.Auto, graphics/ccc.gif SetLastError=true)] public static extern bool CryptAcquireContext(ref IntPtr hCryptProv, String containerName, graphics/ccc.gif String providerName, int providerType, uint provider Flags); 

This piece of code defines a managed static method in our class, also called CryptAcquireContext , that is essentially a proxy for the unmanaged Win32 CryptAcquireContext API. The other arguments to the DllImport attribute are optional but useful. The CharSet attribute defines how we want the platform invoke mechanism to differentiate between Unicode and ANSI versions of APIs and marshal String objects to unmanaged code. (The CharSet.Auto setting tells the CLR to use Unicode on Windows NT, Windows 2000, and Windows XP and ANSI on Windows 98 and Windows ME.) The SetLastError attribute tells the platform invoke mechanism that we want Win32 error information maintained when we call through the proxy. (Error codes can be retrieved with a call to Marshal.GetLastWin32Error() , and they are quite useful when debugging CryptoAPI-based applications.)

NOTE

The IntPtr structure is a platform-specific type that is used to represent a pointer or handle. We use IntPtr objects for HCRYPTPROV and HCRYPTKEY handles in CryptoAPI.


CryptAcquireContext allows us to obtain a handle on a key container within a specific cryptographic service provider, but we also need to be able to call CryptGenKey to generate a key pair within a key container. The API definition for CryptGenKey is as follows:

 BOOL WINAPI CryptGenKey(   HCRYPTPROV hProv,      // handle to key container   ALG_ID Algid,          // algorithm ID (uint)   DWORD dwFlags,     // Flags (uint)   HCRYPTKEY* phKey       // handle to key (out parameter) ); 

The DllImport declaration for accessing CryptGenKey from managed code is as follows:

 [DllImport("advapi32.dll",EntryPoint="CryptGenKey",CharSet=CharSet.Auto, graphics/ccc.gif SetLastError=true)] public static extern bool CryptGenKey(IntPtr hProv, uint AlgID, uint dwFlags, ref IntPtr graphics/ccc.gif phKey); 

With defined managed entry points for both CryptAcquireContext and CryptGenKey , we are now ready to proceed. Our goal is to write a method CreateRSAKey that allows us to specify whether the created key should be exportable and/or UI-protected. The signature for our desired method is as follows:

 public static void CreateRSAKey(int size, String containerName, bool exportable, bool graphics/ccc.gif uiProtected) {    ... } 

For the purposes of this example, we always create the key within the default Type 1 cryptographic service provider. The size and containerName arguments allow us to determine key size and location. The Boolean values exportable and uiProtected , if set to true , will cause appropriate flags to be passed to CryptoAPI.

The first step to accomplish in our method is to obtain a handle (in an IntPtr structure) to the key container in which the key is to be generated. The following two lines of code create a new IntPtr structure and then invoke CryptAcquireContext to obtain a handle on the key container:

 IntPtr hCryptProv = new IntPtr(0); bool acquireSucceeded = CryptAcquireContext(ref hCryptProv, containerName, null, 1, 0); 

The arguments to CryptAcquireContext are a reference to the IntPtr where the handle will be returned, the String value of the desired container name ” null to indicate we want the default cryptographic service provider, 1 to indicate that we want a Type 1 provider, and to indicate that we are not passing any additional flag (at this time) to CryptAcquireContext . The return value acquireSucceeded will be true if the call completed successfully; otherwise , false .

The previous code snippet works if the desired key container already exists, but it will fail if we have to create the key container within the CSP. If the container does not exist, the Win32 error code returned from CryptAcquireContext will be NTE_BAD_KEYSET (defined to be the value 0x80090016 in winerror.h ). We can detect and handle this specific failure case as follows:

 uint CRYPT_NEWKEYSET = 0x00000008; IntPtr hCryptProv = new IntPtr(0); // We assume the key container already exists.  If not, we'll catch the // error and try again with CRYPT_NEWKEYSET specified bool acquireSucceeded = CryptAcquireContext(ref hCryptProv, containerName, null, 1, 0); if (!acquireSucceeded) {   uint error = (uint) Marshal.GetLastWin32Error();   // 0x8009000F = NTE_BAD_KEYSET, which means the key container   // probably doesn't exist, so we'll try to create it   if (error != 0x80090016) {     throw new CryptographicException((int) error);   }   acquireSucceeded = CryptAcquireContext(ref hCryptProv, containerName, null, 1, graphics/ccc.gif CRYPT_NEWKEYSET);   if (!acquireSucceeded) {     throw new CryptographicException(Marshal.GetLastWin32Error());   } } 

This code snippet attempts to open the key container as though it already exists within the provider and, upon failure, attempts to create a new container with the desired name. The CRYPT_NEWKEYSET flag value is defined in the wincrypt.h header file, and we copy that definition into our managed source code for easy reference. The line

 uint error = (uint) Marshal.GetLastWin32Error(); 

returns to us the Win32 error generated by the call to CryptAcquireContext because we set the SetLastError attribute to true on the DllImport attribute that defined the proxy to CryptAcquireContext . We have to cast the error value from int to uint so we can compare it with the uint value of NTE_BAD_KEYSET (defined in winerror.h as 0x80090016 ). If the error we received from CryptoAPI is indeed NTE_BAD_KEYSET , we simply try calling CryptAcquireContext again by specifying the CRYPT_NEWKEYSET flag to cause the key container to be generated. On any other error, or if the second call to CryptAcquireContext fails, we throw a CryptographicException .

After we have successfully called CryptAcquireContext , the IntPtr hCryptProv will contain a handle to the desired key container, and we are ready to generate a new key pair within it. The flags argument to CryptGenKey tells CryptoAPI whether we want the key to be exportable and whether there should be a pop-up dialog shown to the user on every key access; we construct flags by OR ing together the CRYPT_EXPORTABLE and CRYPT_USER_PROTECTED values as appropriate. The result of calling CryptGenKey is a Boolean value indicating success or failure and, if successful, the hCryptKey IntPtr argument will contain a handle to the generated key. The following is sample code for creating the key in the hCryptProv container:

 uint flags = 0; if (exportable) flags = CRYPT_EXPORTABLE; if (uiProtected) flags = CRYPT_USER_PROTECTED; IntPtr hCryptKey = new IntPtr(0); bool genKeySucceeded = CryptGenKey(hCryptProv, 0x0000A400, flags, ref hCryptKey); 

The second argument to the CryptGenKey call, the value 0x0000A400 , is the value of the wincrypt.h constant CALG_RSA_KEYX , which indicates that the key to be generated is an RSA key suitable for "key exchange" and it should be stored in the "key exchange" slot of the key container. ( CALG_RSA_KEYX also appears in Table 31.4.) In this example, we assume that we always want to create "key exchange" keys because they can be used both for signature and encryption operations.

There is one additional piece of information we need to process ”the desired size of the resulting key pair. In CryptoAPI, the desired size of the public modulus is stored as the high 16 bits of the flags argument to CryptGenKey ; with our current code, we always pass a value that is interpreted as "use the default key size" (usually 1024 bits). To handle any valid key size between 384 and 16384 bits (the sizes supported by the Microsoft Strong Cryptographic Provider), we need to add that information into the flags field. The following code checks that the requested key size is valid and modifies flags accordingly :

 // The desired size of the modulus is contained in the upper 16 bits of // the flags field.  First we check that size is between 384 and 16384 (the // size range allowed for Type 1 providers, and then we OR in the bits if ((size < 384)  (size > 16384)) {   throw new ArgumentException("Invalid size parameter."); } flags = ((uint) size << 16); 

We now have all the pieces necessary to create RSA keys with optional exportability of the private component and UI key protection. Listing 31.1 contains a small utility program for creating random keys built from the code snippets we have just discussed. The Main() routine processes command line arguments of -s <size> , -e , and -u to indicate the desired size of the public key modulus (in bits), exportability of the private key, and whether UI protection should be added to the key. The final command line argument is always the name of the key container in which to store the newly generated key.

Listing 31.1 Creating a New RSA Encryption Key Pair Optional Exportability and UI Protection
 using System; using System.Security.Cryptography; using System.Runtime.InteropServices; public class CreateNonExportableKeyClass {   private static uint CRYPT_NEWKEYSET = 0x00000008;   [DllImport("advapi32.dll",EntryPoint="CryptAcquireContext", CharSet=CharSet.Auto, graphics/ccc.gif SetLastError=true)]   public static extern bool CryptAcquireContext(ref IntPtr hCryptProv, String graphics/ccc.gif containerName,     String providerName, int providerType, uint providerFlags);   private static uint CRYPT_EXPORTABLE = 0x00000001;   private static uint CRYPT_USER_PROTECTED = 0x00000002;   [DllImport("advapi32.dll",EntryPoint="CryptGenKey",CharSet=CharSet.Auto, graphics/ccc.gif SetLastError=true)]   public static extern bool CryptGenKey(IntPtr hProv, uint AlgID, uint dwFlags, ref graphics/ccc.gif IntPtr phKey);   public static void CreateRSAKey(int size, String containerName, bool exportable, bool graphics/ccc.gif uiProtected)   {     IntPtr hCryptProv = new IntPtr(0);     // We assume the key container already exists.  If not, we'll catch the     // error and try again with CRYPT_NEWKEYSET specified     uint flags = 0;     if (exportable) flags = CRYPT_EXPORTABLE;     if (uiProtected) flags = CRYPT_USER_PROTECTED;     // The desired size of the modulus is contained in the upper 16 bits of     // the flags field.  First we check that size is between 384 and 16384     // (the size range allowed for Type 1 providers), and then we     // OR in the bits     if ((size < 384)  (size > 16384))     {       throw new ArgumentException("Invalid size parameter.");     }     flags = ((uint) size << 16);     bool acquireSucceeded = CryptAcquireContext(ref hCryptProv, containerName, null, 1, graphics/ccc.gif 0);     if (!acquireSucceeded)     {       uint error = (uint) Marshal.GetLastWin32Error();       // 0x8009000F = NTE_BAD_KEYSET, which means the key container       // probably doesn't exist, so we'll try to create it       if (error != 0x80090016)       {         throw new CryptographicException((int) error);       }       acquireSucceeded = CryptAcquireContext(ref hCryptProv, containerName, null, 1, graphics/ccc.gif CRYPT_NEWKEYSET);       if (!acquireSucceeded)       {         throw new CryptographicException(Marshal.GetLastWin32Error());       }     }     IntPtr hCryptKey = new IntPtr(0);     bool genKeySucceeded = CryptGenKey(hCryptProv, 0x0000A400, flags, ref hCryptKey);     if (!genKeySucceeded)     {       throw new CryptographicException(Marshal.GetLastWin32Error());     }   }   public static void Main(String[] args)   {     if (args.Length < 1)     {       Console.WriteLine("Usage: createkey [-s size] [-e] [-u] keyContainerName");       return;     }     String keyContainerName = null;     bool exportable = false;     bool uiProtected = false;     int i = 0;     int size = 1024;     while (i < args.Length)     {       if (args[i].Equals("-s"))       {         i++;         size = Int32.Parse(args[i]);         i++;         continue;       }       if (args[i].Equals("-e"))       {         exportable = true;         i++;         continue;       }       if (args[i].Equals("-u"))       {         uiProtected = true;         i++;         continue;       }       keyContainerName = args[i];       break;     }     CreateRSAKey(size, keyContainerName, exportable, uiProtected);   } } 

NOTE

The program shown in Listing 31.1 has a couple of obvious limitations. First, as a result of the way the command line arguments are processed , it is not possible to use this program to access a key container called -s , -e , or -u , and, in fact, trying to do so will end up accessing the default key container ( null string value). Second, there is no provision for creating keys in the "signature" slot of a key container, although that could easily be added by adding another command line argument, another Boolean flag to the CreateRSAKey method and, depending on the value of that flag, changing the algorithm ID value from 0x0000A400 to 0x00002400 . Finally, there is no provision in this program for accessing key containers stored in the machine key store, but again, an option could be easily added by OR ing in the CRYPT_MACHINE_KEYSET flag to the flags passed to the CryptAcquireContext call.


Cleaning Up: Deleting Keys and Key Containers

Our final comment on working with CryptoAPI 1.0 has to do with deleting keys and key containers. The .NET Framework does not provide a static method for deleting keys; keys are deleted only if they are stored in a transient key container and then only when a managed object bound to that key container is disposed of (via an explicit call to Clear() or implicitly by the garbage collector). If you want to explicitly delete a key container, you have two options. First, you could create a managed object bound to the key container (by specifying the name of the key container in a CspParameters object), force the key container to be considered transient by setting the PersistKeyInCsp property on the managed object to false , and then explicitly disposing of the managed object through a call to Clear() . The other method of deleting a key container is to explicitly call CryptAcquireContext and with the CRYPT_DELETE_KEYSET flag (value 0x00000010 ). Both mechanisms demand "unmanaged code" permission (an instance of SecurityPermission(SecurityPermissionFlag.UnmanagedCode) ) of their call stack.

This concludes our discussion of working with CryptoAPI 1.0 (base cryptography) from within the .NET Framework. In the next section, we move to CryptoAPI 2.0 functions that relate to digital certificates and signed messages.

for RuBoard


. NET Framework Security
.NET Framework Security
ISBN: 067232184X
EAN: 2147483647
Year: 2000
Pages: 235

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net