The CryptoAPI is a Windows CE API for performing common cryptographic functions such as computing hashes and encrypting or decrypting data. The .NET Compact Framework has no support for cryptographic functions that are accessible to the outside user. Thus, .NET Compact Framework developers must invoke the native CryptoAPI from their managed applications in order to perform cryptographic functions.
The CryptoAPI is a brittle API that will cause confusion for developers who don't understand some basic principles behind its use. The following sections review those basics before the rest of the chapter jumps into using the CryptoAPI.
Like many other functional areas in the Windows operating system, the CryptoAPI is driven by the concept of a handle. For example, encrypting data requires a handle to the cryptographic provider and a handle to the specific encryption key to be used. Programming against the CryptoAPI is really just the process of acquiring the correct handles from some functions and passing them in as arguments to other functions to achieve the desired results. However, the rules for using the handles correctly are very specific, and function calls fail if the handles are used incorrectly.
The CryptoAPI architecture provides a standard interface for calling into cryptography functions, regardless of what the underlying algorithms are. Groups of algorithms are bundled together into packages called cryptographic service providers, or CSPs. The CSP used in the ManagedCryptoAPI wrapper class is the PROV_RSA_FULL provider. PROV_RSA_FULL provides encryption and hashing algorithms that are patented by the RSA and provided by Microsoft under license. It is a good general-purpose provider, and it is ubiquitous on Windows CE devices.
Other commonly used CSPs are shown in Table 14.1. Additionally, developers can write their own CSPs, and then anyone can access their algorithms through CryptoAPI.
Not all of the CSPs shown in Table 14.1 are included by default with Windows CE and Pocket PC devices. It is because the PROV_RSA_FULL provider is so common that we use it exclusively in the ManagedCryptoAPI wrapper.
PROV_RSA_FULL | PROV_RSA_SIG |
PROV_DSS | PROV_DSS_DH |
PROV_SSL | PROV_EC_ECDSA_SIG |
PROV_EC_ECNRA_SIG | PROV_EC_ECDSA_FULL |
PROV_SPYRUS_LYNKS | PROV_FORTEZZA |
PROV_MSEXCHANGE | PROV_RSA_CHANNEL |
To perform any cryptographic function with the CryptoAPI, you must first acquire a context by calling CryptAcquireContext. Acquiring a context serves two purposes. First, it communicates to the CryptoAPI which CSP you want to use. Second, it tells the CryptoAPI which key container to use. A key container is a location where encryption keys are internally stored by CryptoAPI. As a developer, you usually interact with a key through its handle, and you let the CryptoAPI store the bytes of the key internally.
The DllImport definition for CryptAcquireContext in ManagedCryptoAPI looks like this:
C# [DllImport("coredll.dll")] private static extern bool CryptAcquireContext(ref IntPtr phProv, string pszContainer, string pszProvider, Int32 dwProvType, uint dwFlags); VB Declare Function CryptAcquireContext Lib "coredll.dll" (ByRef phProv As IntPtr, ByVal pszContainer As String, ByVal pszProvider As String, ByVal dwProvType As UInt32, ByVal dwFlags As UInt32) As Boolean
When acquiring a context, you may choose to use the default key container, or you may pass in a string name for a key container. It is strongly recommended that you pass in a name for the key container because other software on your device could already be using the default key container and you could inadvertently clobber the keys used by the other software. Specifically, the Pocket PC driver software for several brands of wireless network cards uses the default key container. If you also use the default container, you risk clobbering the keys that the driver software uses to encrypt wireless traffic, which would cause your wireless card to stop working suddenly.
ManagedCryptoAPI has two methods for acquiring a context. AcquireDefaultContext returns an IntPtr, which holds the handle to the default key container for the PROV_RSA_FULL provider. AcquireNamedContext returns an IntPtr, which holds the handle to a named key container that you pass in as a string. Programs retain the returned handle and pass it in to other methods that are discussed later in this chapter. The code for AcquireDefaultContext is shown in Listing 14.1.
C# public IntPtr AcquireDefaultContext() { IntPtr hProvider = IntPtr.Zero; if (!CryptAcquireContext(ref hProvider, null, "Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, 0)) { // We might have to create a new keyset... if (!CryptAcquireContext(ref hProvider, null, "Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, CRYPT_NEWKEYSET)) { // Big trouble, could not create a new // keyset and could not acquire default keyset throw new Exception("ManagedCryptoAPI cannot access default keyset or create new default keyset!"); } } return hProvider; } VB Public Function AcquireDefaultContext() As IntPtr Dim hProvider As IntPtr = IntPtr.Zero If (CryptAcquireContext(hProvider, Nothing, "Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, Convert.ToUInt32(0)) = False) Then Dim l_Failure As String = TranslateErrorCode(Convert.ToInt64 (GetLastError())) ' We might have to create a new keyset... If (CryptAcquireContext(hProvider, Nothing, "Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, CRYPT_NEWKEYSET) = False) Then ' Big trouble, could not create a new keyset and could not acquire ' default keyset Throw New Exception("ManagedCryptoAPI cannot access default keyset or create new default keyset!" + TranslateErrorCode (Convert.ToInt64(GetLastError()))) End If End If Return hProvider End Function |
The code for AcquireNamedContext is in Listing 14.2.
C# public IntPtr AcquireNamedContext(string in_ContainerName) { IntPtr hProvider = IntPtr.Zero; if (!CryptAcquireContext(ref hProvider, in_ContainerName, "Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, 0)) { if (!CryptAcquireContext(ref hProvider, in_ContainerName, "Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, CRYPT_NEWKEYSET)) { throw new Exception("ManagedCryptoAPI cannot access default keyset or create new default keyset!"); } } return hProvider; } VB Public Function AcquireNamedContext(ByVal in_ContainerName As String) As IntPtr Dim hProvider As IntPtr = IntPtr.Zero If (CryptAcquireContext(hProvider, in_ContainerName, "Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, Convert.ToUInt32(0)) = False) Then Dim l_Failure As String = TranslateErrorCode(Convert.ToInt64 (GetLastError())) ' We might have to create a new keyset... If (CryptAcquireContext(hProvider, in_ContainerName, "Microsoft Base Cryptographic Provider v1.0", PROV_RSA_FULL, CRYPT_NEWKEYSET) = False) Then ' Big trouble, could not create a new keyset and could not acquire ' default keyset Throw New Exception("ManagedCryptoAPI cannot access default keyset or create new default keyset!" + TranslateErrorCode(Convert.ToInt64 (GetLastError()))) End If End If Return hProvider End Function |
CryptAcquireContext returns a handle to a key container within a CSP. If the context for the key container that is asked for has never been acquired in the past, then the key container does not exist yet. In this case the key container must be created. AcquireDefaultContext and AcquireNamedContext both check for failure when calling CryptAcquireContext and try to create the key container if the first call fails. If the attempt to create the key container fails, then the methods throw an exception.
It is not a good practice to always attempt to create a new key container when calling CryptAcquireContext, because if the key container already exists, then the call to CryptAcquireContext will fail. Thus, it is very important to try to access the key container, assuming it does exist, and to try to create it only if necessary.
18.188.151.107