Application Programmer's Guide
Microsoft CryptoAPI
Preliminary
Version 0.9
January 17, 1996
Microsoft Corporation
Note This documentation is an early release of the final product documentation and is only for limited distribution outside of Microsoft and not for redistribution. It is meant to accompany software still in development. Some of the information in the documentation may be inaccurate or may not be an accurate representation of the functionality in the final released product. Microsoft assumes no responsibility for any damages that might occur either directly or indirectly from these inaccuracies.
This is a preliminary document and may be changed substantially prior to final commercial release. This document is provided for informational purposes only and Microsoft Corporation makes no warranties, either express or implied, in this document. The entire risk of the use or the results of the use of this document remains with the user. Companies, names, and data used in examples herein are fictitious unless otherwise noted. No part of this document may be reproduced or transmitted in any form or by any means, electronic or mechanical, for any purpose, without the express written permission of Microsoft Corporation.
Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. The furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.
© 1995 Microsoft Corporation. All rights reserved.
Microsoft, MS, MS-DOS, Visual Basic, Win32, and Windows are registered trademarks, and Visual C++ and Windows NT are trademarks of Microsoft Corporation in the U.S.A. and other countries.
TrueType is a registered trademark is a registered trademark of Apple Computer, Inc.
Unicode is a trademark of Unicode, Incorporated.
All other trademarks are the property of their respective owners.
Printed in the United States of America.
January 17, 1996
Contents
CHAPTER 0
The Microsoft Cryptographic API (CryptoAPI) provides services that enable application developers to add cryptography to their Win32 applications. Applications can use the functions in CryptoAPI without knowing anything about the underlying implementation, in much the same way that an application can use a graphics library without knowing anything about the particular graphics hardware configuration.
Audience
This document is intended to be used by developers familiar with the Microsoft Windows programming environment. Previous experience with cryptography or other security related subjects is helpful, but not absolutely necessary.
Document Overview
This guide presents general information about how to incorporate cryptography into applications and offers specific information about the function data types of the Microsoft Cryptographic API (CryptoAPI). Following the introduction, this guide is divided into the following sections:
Chapter 1, Overview of Cryptography
This explains the basics of cryptography and focuses on the techniques and functionality supported by CryptoAPI.
Chapter 2, The CryptoAPI Programming Model
This introduces each major functional area and provides general guidelines for writing a secure application.
Chapter 3, Interfacing with a Cryptographic Service Provider (CSP)
This explains how applications interface with a CSP. The capabilities of the Microsoft RSA Base Provider are also explained.
Chapter 4, Generating Cryptographic Keys
This explains how to create, configure, and destroy cryptographic keys.
Chapter 5, Exchanging Cryptographic Keys
This explains how to export and import cryptographic keys from the CSP. The authenticated key exchange protocol is also covered.
Chapter 6, Encrypting and Decrypting Data
This explains some of the more common encryption techniques and how to use CryptoAPI to perform encryption and decryption of data.
Chapter 7, Hashes and Digital Signatures
This explains how to use CryptoAPI to sign data and keys.
Chapter 8, System Administration
This outlines how different CSPs are installed onto the system.
Chapter 9, Service Provider Functions
This explains each of the functions used by applications to connect to a cryptographic service provider.
Chapter 10, Key Generation and Exchange Functions
This explains each of the functions used to generate and exchange cryptographic keys. The material in this section directly relates to the procedures discussed in Chapter 4 and Chapter 5.
Chapter 11, Data Encryption Functions
This explains each of the functions used to encrypt and decrypt data.
Chapter 12, Hashing and Digital Signature Functions
This explains each of the functions used to create digital signatures as well as the ones used to create message digests (hashes) from data.
Chapter 13, Data Types and Constants
This explains some of the data types that are unique to CryptoAPI.
Document Conventions
The type conventions used in this document are as follows:
Bold Bold letters or text indicate a specific term to use, as in functions (for example, CryptGenKey), and structure fields. Data types such as HCRYPTHASH are shown in both bold and uppercase. You must enter these terms exactly as shown. ALL CAPS The names of constants such as CALG_RC2, CRYPT_EXPORTABLE, and NULL are given in all upper-case letters, not in boldface type. These terms must be entered exactly as shown. Italic In introductory and explanatory text, italicized words indicate that a key term or concept is being introduced. In describing functions and messages, words in italic indicate a place holder where you need to provide the actual value. For example, the following syntax for the CryptGenRandom function indicates that you must substitute values for the hProv, dwLen, and pbBuffer parameters: CryptGenRandom(HCRYPTPROV hProv, DWORD dwLen, BYTE *pbBuffer) Monospaced text Monospaced type is used to display code samples and data structure definitions.
Related Documentation
Additional documents that will help you understand cryptography and the associated security issues include:
Bruce Schneier, Applied Cryptography, John Wiley & Sons, 1996.
Microsoft Cryptographic Service Provider Programmer's Guide, Microsoft, 1995.
D. R. Stinson, Cryptography: Theory and Practice, CRC Press, 1995.
R. Anderson, "Why Cryptosystems Fail," Communications of the ACM, v. 37, n. 11, November 1994, pp. 32-40.
RSA Laboratories, Public-Key Cryptography Standards, RSA Data Security, November 1993.
Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Introduction to Algorithms, MIT Press, 1990.
D.W. Davies and W.L. Price, Security for Computer Networks, John Wiley & Sons, 1989.
Dorothy E. Denning, Cryptography and Data Security, Addison-Wesley, 1982.
CHAPTER 1
Cryptography provides a set of techniques for encoding data and messages such that the data and messages can be stored and transmitted securely. This section introduces the basic terminology of cryptography and explains some of the common methods used.
Cryptography can be used to achieve secure communications, even when the transmission media (for example, the Internet) is untrustworthy. You can also use cryptography to encrypt your sensitive files, so that an intruder cannot understand them.
Cryptography can be used to ensure data integrity as well as maintain secrecy.
Using cryptography, it becomes possible to verify the origin of data and messages. This is done using digital signatures, which are explained a little later in this chapter.
When using cryptographic methods, the only part that must remain secret is the cryptographic keys. The algorithms, the key sizes, and file formats can be made public without compromising security.
Data Encryption
In using data encryption, a plaintext message can be encoded so it appears like random gibberish and is very difficult to transform back to the original message, without a secret key. In this document, the term message is used to refer to any piece of data. This message can consist of ASCII text, a database file, or any data you want to store or transmit securely. Plaintext is used to refer to data that has not been encrypted, while ciphertext refers to data that has.
Once a message has been encrypted, it can be stored on nonsecure media or transmitted on an nonsecure network, and still remain secret. Later, the message can be decrypted into its original form. This process is shown in the following illustration:
Encryption/Decryption Process
When a message is encrypted, an encryption key is used. This is analogous to the physical key that is used to lock a padlock. To decrypt the message, the corresponding decryption key must be used. It is very important to properly restrict access to the decryption key, because anyone who possesses it will be able to decrypt all messages that were encrypted with the matching encryption key. Note that the encryption and decryption keys are often the same key.
This may come as a surprise, but data encryption/decryption is pretty straight-forward. The really difficult part is keeping the keys safe and transmitting them securely to other users. This is discussed further in Chapter 5.
Symmetric Versus Public-Key Encryption
There are two main classes of encryption algorithms: symmetric algorithms and public-key algorithms (also known as asymmetric algorithms). Systems that use symmetric algorithms are sometimes referred to as conventional.
Symmetric Algorithms
Symmetric algorithms are the most common type of encryption algorithm. They are known as symmetric because the same key is used for both encryption and decryption. Unlike the keys used with public-key algorithms, symmetric keys are frequently changed. For this reason, they are referred to here as session keys.
Compared to public-key algorithms, symmetric algorithms are very fast and, thus, are preferred when encrypting large amounts of data. Some of the more common symmetric algorithms are RC2, RC4, and the Data Encryption Standard (DES).
Public-Key Algorithms
Public-key (asymmetric) algorithms use two different keys: the public key and the private key. The private key is kept private to the owner of the key pair, and the public key can be distributed to anyone who requests it. If one key is used to encrypt a message, then the other key is required to decrypt the message.
Public-key algorithms are very slow, on the order of 1000 times slower than symmetric algorithms. Consequently, they are normally used only to encrypt session keys. They are also used to digitally sign messages, as discussed in the next section.
One of the most common public-key algorithms is the RSA Public-Key Cipher.
Digital Signatures
Digital signatures can be used when you have a message that you plan to distribute in plaintext form, and you want the recipients to be able to verify that the message comes from you and that it hasn't been tampered with since it left your hands. Signing a message does not alter the message, it simply generates a digital signature string you can bundle with the message or transmit separately.
Digital signatures are generated using public-key signature algorithms. A private key is used to generate the signature, and the corresponding public key is used to validate the signature. This process is shown in the following illustration:
Signature Generation/Validation Process
On a network, there is often a trusted application running on a secure computer that is known as the Certification Authority. This application knows the public key of each user. Certification Authorities dispense messages known as certificates, each of which contains the public key of one of its client users. Each certificate is signed with the private key of the Certification Authority. A certificate containing the public key of the signer is often bundled with a signed message to make it easier to verify the signature. (Certificates are described in more detail in Chapter 5.)
CHAPTER 2
The CryptoAPI Programming Model
The Microsoft Cryptographic Application Program Interface (CryptoAPI) is a set of functions that allow applications to encrypt or digitally sign data in a flexible manner, while providing protection for the user's sensitive private key data.
All cryptographic operations are performed by independent modules known as cryptographic service providers (CSPs). One CSP, the Microsoft RSA Base Provider, is bundled with the operating system.
Each CSP provides a different implementation of the CryptoAPI. Some provide stronger cryptographic algorithms while others contain hardware components such as smartcards. In addition, some CSPs may occasionally communicate with users directly, such as when digital signatures are performed using the user's signature private key.
The CryptoAPI programming model can be compared to the Windows GDI model in that the CSPs are analogous to graphics device drivers, and the cryptographic hardware (optional) is analogous to graphics hardware. Just as well-behaved applications are not allowed to communicate with graphics device drivers and hardware, well-behaved applications cannot directly access the CSPs and cryptographic hardware.
System Architecture
The Microsoft cryptographic system is composed of a number of different components, as shown in the following illustration. The three executable portions are the application itself, the operating system, and the CSP.
Applications communicate with the operating system through a set of functions known as the cryptographic application program interface (CryptoAPI). The operating system, in turn, communicates with CSPs through a set of functions known as the cryptographic service provider interface (CryptoSPI).
Cryptographic System Architecture
Note that applications do not communicate with CSPs directly. Instead, all cryptographic function calls are routed through the operating system. A parameter in each CryptoAPI function indicates to the operating system which CSP to use to perform the actual cryptographic operation.
Cryptographic Service Providers (CSPs)
As mentioned above, CSPs are independent modules that perform the real cryptographic work. Ideally, they are written to be completely independent of any particular application, so that any given application will run with a variety of CSPs. In reality however, some applications may have very specific requirements that require a custom CSP.
The physical manifestation of a CSP consists of, at a minimum, a dynamic-link library (DLL) and a signature file. The signature file is necessary to ensure that the operating system recognizes the CSP. The operating system validates this signature periodically to ensure that the CSP has not been tampered with.
Some CSPs may implement a fraction of their functionality either in an address separated service called through local RPC, or in hardware called through a system device driver. Isolating global key state and central cryptographic operations in hardware or in a service keeps keys and operations safe from tampering within the application data space.
Applications should not take advantage of attributes particular to a specific CSP. For example, the Microsoft RSA Base Provider currently uses 40-bit session keys and 512-bit public keys. When applications manipulate these, they should be careful not to make assumptions about the amount of memory needed to store them. Otherwise, the application is likely to fail when the user loads a different CSP onto the system. You should take care to write applications that are as well-behaved and flexible as possible.
Key Databases
Each CSP has a key database in which it stores its persistent cryptographic keys. Each key database contains one or more key containers, each of which contain all the key pairs belonging to a specific user (or CryptoAPI client). Each key container is given a unique name, which applications provide to the CryptAcquireContext function when acquiring a handle to the key container. Following is an illustration of the contents of a key database:
Contents of a Key Database
The CSP stores each key container from session to session, including all the public/private key pairs it contains. However, session keys are not preserved from session to session.
Generally, a default key container is created for each user. This key container takes the user's logon name as its own name which is then used by any number of applications. It is also possible for an application to create its own key container (and key pairs) which it usually names after itself.
Session Keys
Session keys are used when encrypting and decrypting data. They are created by applications using either the CryptGenKey or the CryptDeriveKey function. These keys are kept internal to the CSP for safekeeping.
Unlike the key pairs, session keys are volatile. Applications can save these keys for later use or transmission to other users by exporting them from the CSP into application space in the form of an encrypted "key blob" using the CryptExportKey function. (This procedure is discussed in Chapter 5.)
Public/Private Key Pairs
Each user generally has two public/private key pairs. One key pair is used to encrypt session keys and the other to create digital signatures. These are known as the key exchange key pair and the signature key pair, respectively. (These keys pairs are discussed later in detail.)
Note that the while key containers created by most CSPs will contain two key pairs, this is not required. Some CSPs do not store any key pairs while others store additional ones.
Using Cryptography in your Applications
The Microsoft Cryptographic Application Program Interface (CryptoAPI) specifies functions in a number of different areas:
Context Functions
These functions are used by applications to connect to a CSP. These functions enable applications to choose a specific CSP by name, or get one with a needed class of functionality. (See Chapter 3 and Chapter 9.)
Key Generation Functions
These functions allow applications to generate and customize cryptographic keys. Full support is included for changing chaining modes, initialization vectors, and other encryption features. (See Chapter 4 and Chapter 10.)
Key Exchange Functions
These functions allow applications to exchange or transmit keys. These functions can also be used to implement fully authenticated three-leg key exchange. (See Chapter 5 and Chapter 10.)
Data Encryption Functions
These functions allow applications to encrypt or decrypt data. Support is also included for simultaneously encrypting and hashing data. (See Chapter 6 and Chapter 11.)
Hashing and Signature Functions
These functions allow applications to compute cryptographically secure digests of data and also enable digital signing of data. (See Chapter 7 and Chapter 12.)
Interfacing with a Cryptographic Service Provider (CSP)
The CSP architecture provides a safe way for multiple applications to access cryptographic and signature services. Instead of being passive sets of encryption routines, CSPs are independently functioning cryptographic modules capable of authenticating the user and checking for user assent to actions.
For example, some CSPs will require a PIN to be entered before a digital signature is generated, while some require a smart card, and still others have no authentication at all. The quality of protection for keys within the system is a design parameter of the CSP itself and not the system as a whole. This lets the same applications run in a variety of security contexts without modification.
The amount of access that applications have to the cryptographic internals has been carefully restricted. This was done to facilitate writing applications that are both secure and portable. The following three design rules apply:
Applications cannot directly access keying material. Because all keying material is generated within the CSP and used by the application through opaque handles, there is no risk of an application or its associated DLLs either divulging keying material or choosing keying material from poor random sources.
Applications cannot specify the details of cryptographic operations. The CSP interface only allows applications to specify broad actions to take (for example, encrypt data using algorithm X and sign data). The actual implementation of the cryptographic operations is the responsibility of the CSP. This limits the scope of the API because esoteric protocols require application intervention, but make a basic set of operations readily available to all applications.
Applications do not handle user authentication data. User authentication is done by the CSP. In this way, CSPs that have better authentication capabilities (for example, biometric inputs and data keys) will function without needing to change the application's authentication model. It also prevents applications from divulging user secrets.
Different Types of CSPs
Each provider has both a name and a type. For example, the name of the CSP currently shipped with the operating system is "Microsoft Base Cryptographic Provider v1.0," and its type is PROV_RSA_FULL. The name of each provider is unique; the provider type is most definitely not.
Provider Types
The field of cryptography is very large. There are dozens of different "standard" data formats and protocols. These are generally organized into groups or "families," each of which has its own set of data formats and way of doing things. Even if they use the same algorithm (for example, the RC2 block cipher), two families will often use a different padding scheme, different key lengths, and different default modes. CryptoAPI has been designed so each CSP type represents a particular family.
When an application connects to a CSP of a particular type, each of the CryptoAPI functions will, by default, operate in a way prescribed by the "family" that corresponds to the CSP type. Among other things, an application's choice of provider type specifies the following items:
Key exchange algorithm - Each provider type specifies one and only one key exchange algorithm. Every CSP of a particular type must implement this algorithm. The only way applications can specify which key exchange algorithm is used is by selecting a CSP of the appropriate provider type.
Digital signature algorithm - This is the same as with the key exchange algorithm. Each provider type specifies one and only one digital signature algorithm.
Key blob format - When a public key or session key is exported out of a CSP, the format of the resulting "key blob" is specified by the provider type.
Digital signature format - The provider type prescribes a particular digital signature format. This ensures that a signature produced by a CSP of a given provider type can be verified by any CSP of the same provider type.
Session key derivation scheme - When a key is derived from a hash, the method used is specified by the provider type.
Key length - Some provider types will specify that the public/private key pairs or the session keys be of a certain length.
Default modes - The provider type will often specify a default mode for various options, such as the block encryption cipher mode or the block encryption padding method.
Each application will generally work only with a single type of CSP. (However, an ambitious application can connect to more than one CSP at a time.) When writing an application, you will often need to obtain all the documentation that relates to the CSP type you are using. For example, it is not recommended that you try to write an application using the PROV_RSA_FULL provider type without obtaining the Public-Key Cryptographic Standards (PKCS) from RSA Data Security, Inc. The relevant third-party documentation for each provider type is listed later on in this section.
Predefined Provider Types
A number of provider types have already been defined. The following table lists these provider types, along with the algorithms that each type must support. A CSP of a given type is free to support other algorithms in addition to the ones listed.
Provider Type Key Signature Encryption Hashing Exchange PROV_RSA_FULL RSA RSA RC2, RC4 MD5, SHA PROV_RSA_SIG n/a RSA n/a MD5, SHA PROV_DSS n/a DSS n/a SHA PROV_FORTEZZA KEA DSS Skipjack SHA PROV_MS_MAIL RSA RSA CAST MD5 PROV_SSL RSA RSA varies varies
If two or more applications plan to exchange keys and encrypted messages, they should both use CSPs of the same type, however, some CSP types may be partially compatible with others.
Anyone writing a custom CSP can define a new provider type. However, this person is then responsible for distributing the new provider type to the authors of any applications that are to use it.
In the event that the previous table mentioned algorithms you are not familiar with, the following table provides a brief description of each.
Algorithm Description CAST This is a 64-bit symmetric block cipher developed by C. M. Adams and S. E. Tavares. This algorithm is somewhat similar to DES (Data Encryption Standard). DES National Institute of Standards and Technology (NIST) Data Encryption Standard. This is a 64bit symmetric block cipher that has a fixed key length of 56-bits. DH Diffie-Hellman. This is a public-key algorithm used for secure key exchange. It cannot be used for data encryption. DSS Digital Signature Standard. This standard uses the Digital Signature Algorithm (DSA), which is a public-key cipher used to generate digital signatures. It cannot be used for data encryption. KEA Key Exchange Algorithm. This is an improved version of Diffie-Hellman. MD2 MD2. This is a hashing algorithm that produces a 128bit hash value. MD4 MD4. This is a hashing algorithm that produces a 128bit hash value. MD5 MD5. This is an improved version of MD4. It also produces a 128bit hash value. RC2 RC2 Block Cipher. This is a 64bit symmetric block cipher. RC4 RC4 Stream Cipher. This is a symmetric stream cipher. RSA RSA Public-Key Cipher. This is a popular public-key cipher used for both encryption and signatures. SHA Secure Hash Algorithm. This is a hashing algorithm that produces a 160bit hash value. Skipjack This is the algorithm used by the Clipper and Capstone chips. It is a symmetric block cipher with a fixed key length of 80 bits.
PROV_RSA_FULL Provider Type
This provider type supports both digital signatures and data encryption, and considered general purpose. The RSA public-key algorithm is used for all public-key operations.
This provider type has been thoroughly defined by Microsoft and RSA Data Security. It is described in the following documents:
Microsoft Cryptographic Service Provider Programmer's Guide, Microsoft, 1995.
RSA Laboratories, Public-Key Cryptography Standards, RSA Data Security, November 1993.
PROV_RSA_SIG Provider Type
This provider type is a subset of PROV_RSA_FULL. Only those functions and algorithms required for hashes and digital signatures are supported.
PROV_DSS Provider Type
This provider type is similar to PROV_RSA_SIG in that it only supports hashes and digital signatures, but the signature algorithm specified by the PROV_DSS provider type is the Digital Signature Algorithm (DSA).
The DSA algorithm was proposed by the National Institute of Standards and Technology (NIST). A description of the algorithm can be found in many books on cryptography or from the following government reference:
"Proposed Federal Information Processing Standard for Digital Signature Standard (DSS)," Federal Register, v. 57, no. 21, 31 Jan 1992, pp. 3747-3749.
PROV_FORTEZZA Provider Type
The set of cryptographic protocols and algorithms known as "Fortezza" is "owned" by the National Institute of Standards and Technology (NIST). More information is available directly from them or from other sources.
PROV_MS_MAIL Provider Type
CSPs of this type are designed to cater to the cryptographic needs of the Microsoft Mail application, as well as other applications that are compatible with MS Mail. This provider type is preliminary.
PROV_SSL Provider Type
CSPs of this type support the Secure Sockets Layer (SSL) protocol. A specification explaining the SSL protocol is available from Netscape Communications Corp.
The Microsoft RSA Base Provider
The Microsoft RSA Base Provider is supplied by Microsoft and is included with the operating system (either Windows NT or Windows 95).
The Microsoft RSA Base Provider consists of a software implementation of the PROV_RSA_FULL provider type. The RSA public-key cipher is used for both key exchange and digital signatures, with a key length of 512 bits. The RC2 and RC4 encryption algorithms are implemented with a key length of 40 bits. The MD2, MD5, and SHA hashing algorithms are also provided.
Connecting to a Cryptographic Service Provider
Each time an application is run, the first CryptoAPI function an application calls is the CryptAcquireContext function. This function returns to the application a handle to a particular CSP. In addition, this handle specifies a particular key container within the CSP. If the CSP has just been installed and no key containers yet exist, the CryptAcquireContext function can also be used to create a new one.
When an application uses CryptAcquireContext to obtain a CSP handle, it specifies a provider type and, optionally, a provider name. If both a type and a name are specified, then the function looks for a CSP with precisely the same type and name, loads it into memory, and returns a handle to the application.
When an application calls CryptAcquireContext specifying a provider type but no provider name, the function tries to find the provider name, first on a list of default providers associated with the logged-on user and, if that fails, from a list of default providers associated with the computer.
Once the provider name has been determined successfully, the CryptAcquireContext function searches for the CSP, loads it into memory, and returns a handle to the application.
CHAPTER 4
Keys lie near the center of almost every cryptographic operation. It is important to keep these secret because whoever possesses a given key then has access to any data that key is associated with. For example, if a key is used to encrypt a file, then anyone with a copy of that key can easily decrypt the file. Furthermore, if the key is used to sign messages, then anyone possessing that key can forge the signatures.
Cryptographic Key Overview
There are two types of cryptographic keys: session keys and public/private key pairs.
Session Keys
Session keys are primarily used for data encryption/decryption and are used with symmetric encryption algorithms. That is, the same key is used for both encryption and decryption.
Most of the activity involving session keys relates to keeping them secret. It is important to keep the number of people who possess a particular session key as small as possible (one or two people is recommended).
Public/Private Key Pairs
Key pairs are composed of two components: the public key and the private key. The public key is distributed far and wide while the private key, on the other hand, is kept secret. Only the owner of the key pair is allowed to possess the private key.
If one of the keys (the public key) is used to encrypt a message, then the other key is required to decrypt it. Thus, if you want to send someone a message, you can encrypt the file using their public key and be confident that no one else will be able to read the file.
If the private key is used to sign a message, then the other key must be used to validate the signature. For example, if you want to send someone a digitally signed message, you would sign the message with your private key, and the other person could verify your signature using your public key.
Unfortunately, public-key algorithms are incredibly slow and it is impractical to use them to encrypt large amounts of data. In practice, symmetric algorithms are used for encryption/decryption, while the public-key algorithms are used merely to encrypt the session keys. Similarly, it is not practical to use public-key signature algorithms to sign large messages. Instead, a hash is made of the message and the hash value is signed.
Using Keys With CryptoAPI
All keys are stored within cryptographic service providers (CSPs). CSPs are also responsible for creating the keys, destroying them, and using them to perform a variety of cryptographic operations. (Exporting keys out of the CSP so they can be sent to other users is discussed in Chapter 5.)
Session Keys
Applications can create any number of session keys, which can be used to encrypt messages. However, these keys are not preserved by the CSP from session to session. If you want to use a key for long periods, you need to export the key out of the CSP and into your application for long-term storage. (The procedure for doing this is discussed in Chapter 5.)
Session keys are created using either the CryptGenKey or the CryptDeriveKey function. When these keys are generated, it is necessary to specify the algorithm to use for any subsequent encryption/decryption operations. This algorithm must be one of the symmetric algorithms supported by the CSP being used.
Public/Private Key Pairs
For each user, the CSP usually maintains two public/private key pairs. These keys are maintained from session to session.
The key exchange key pair (also known as the exchange key) is used to encrypt session keys so that they can be safely stored and exchanged with other users. (This is discussed in detail in Chapter 5.)
The digital signature key pair (also known as the signature key) is used to sign either hashes of data. This is discussed in detail in Chapter 7.)
There are a number of reasons for having two separate key pairs. For example, some CSPs may opt to use one algorithm for key exchange and another for digital signatures. This is because some digital signature algorithms are unsuitable for encryption or key exchange, and vise versa. Also, if some data (for example, a session key) is both signed and encrypted with the same public key pair, subtle weaknesses could be introduced that make the data vulnerable.
The exchange key and the signature key pairs are created by calling the CryptGenKey function and specifying either AT_KEYEXCHANGE or AT_SIGNATURE. The CSP implements these keys in an application-independent manner. Applications are not permitted to know the details about the algorithm used.
This section discusses those situations when you must export keys from the secure environment of the cryptographic service provider (CSP) into your application's data space. Keys that have been exported are stored in encrypted data structures known as key blobs. These are discussed in the "Key Blobs Explained" section.
There are two specific situations when it is necessary to export keys:
You want to save a session key for later use by your application. For example, if your application has just encrypted a database file and you want your application to decrypt this file at a later time, your application is responsible for storing the encryption key. This is necessary because CSPs do not preserve symmetric keys from session to session.
You want to send a key to someone else. This would be much easier (for your application) if the respective CSPs could communicate directly, but they cannot. This means the key has to be exported from your CSP, transmitted by your application to the destination application, and then imported into the destination CSP. If you don't trust the communication path, this can become somewhat complicated. However, this is covered in the next few sections.
Note
This section assumes that users (or CryptoAPI client) already possess their own set of public/private key pairs. Instructions for creating these can be found in Chapter 4.
Storing Session Keys
This section discusses how you use CryptoAPI to store a session key for later use. This is useful in those situations where you have encrypted a file using a key and want to decrypt the file at a later time. Another possible situation is one in which you have shared the session key with another user and you want to use the key at a later time to send the other user encrypted messages.
In either case, your application will have to store the session key outside of the CSP for a certain period. Following is the procedure for storing a session key.
1. Create a simple key blob using the CryptExportKey function. This will transfer the session key from the CSP to your application's memory space. Specify that your own key exchange public key be used to encrypt the key blob.
2. Store the signed key blob to disk. The assumption is made here that all disks are nonsecure.
3. Later, when you need to use the key, read the key blob from disk.
4. Import the key blob back into the CSP using the CryptImportKey function.
If the session key is just to be bundled with an encrypted file (so that you can later decrypt the file), and the key is not going to be used to encrypt any more data, then the above procedure provides adequate security.
If you plan to use the session key for encryption at a later time, then the key blob should be signed with your key exchange key before the key is stored to disk. When you later read the key blob back from disk, you should validate the signature to make sure the key blob is intact. If these steps are omitted, then someone with access to your storage media can create their own session key, encrypt it with your key exchange public key, and substitute it for your key blob. You could then unknowingly use their session key to encrypt files and messages, which the unscrupulous user could then easily decrypt. (Digital signatures are discussed in detail in Chapter 7.)
Alternatives to Storing Session Keys
Instead of storing a random session key blob, a derived key can be used. Derived session keys are created from a password using the CryptDeriveKey function. In this way, instead of storing a particular derived key, an application can create a derived key as needed by prompting the user for the password.
Stored key blobs are dependent on the stability of the public/private key pairs stored within the CSP. If these key pairs are somehow lost, (for example, through a hardware or software incident), you will be unable to decrypt your key blobs. This means that any data that has been encrypted using these keys will also be lost. For this reason, it is recommended that you use a backup authority when storing long-term archival data.
A backup authority is a trusted application running on a secure computer which provides storage for the session keys of its clients. All session keys stored there are encrypted in the form of key blobs with the backup authority's public key. An application using a backup authority typically follows these steps:
1. Encrypt the file normally.
2. Export the session key used to encrypt the file into a simple key blob, specifying that your own key exchange public key be used to encrypt the key blob. Store this key blob with the encrypted file.
3. Export the session key again, this time specifying that the backup authority's public key be used to encrypt the key blob. Send this key blob to the backup authority, along with the key's description, serial number, etc.
If, at a later time, you lose your key pairs, you can retrieve the session keys from the backup authority. (You will first have to establish your identity to the backup authority, but this procedure falls outside the scope of CryptoAPI.)
Exchanging Public Keys
Exchanging public keys is the first step that two users contemplating encrypted communication need to do. Once this has been done, the users can send encrypted and signed data to each other in a straightforward manner.
There are two fundamental ways to obtain each other's public keys:
The users can obtain each other's keys in the form of certificates, from a certification authority. This is the most secure way to exchange public keys which does not require user interaction.
The users can read their public keys to each other over the phone, use certified mail to send them to each other, or use another method that is reasonably tamperproof. Note that your public key is not secret, so that it doesn't matter if it is overheard by a third party.
This method can also be used to validate the public key values that have been exchanged in some other manner.
Certificates and Certification Authorities
A certificate is a packet of data that contains a user's public key in addition to the data that serves to identify the user in the real world (for example, the user's name). Every certificate is created and signed by a trusted application known as a certification authority.
Because certificates are signed, they can be transmitted over a nonsecure network or stored on nonsecure media. Each time you receive a certificate, you should verify the signature using the certification authority's public key.
Certain details relating to certificates are beyond the scope of CryptoAPI. These include:
The actual binary format of certificates is not specified by CryptoAPI, although ISO X.509 is recommended.
The manner in which each client communicates his or her name and public keys to the certification authority is not specified.
The manner in which the certification authority's public key is distributed is not specified.
Warning
If you use the Microsoft RSA Base Provider to create a certification authority, your license to issue certificates is limited to certificates intended for use in the context of your particular application or service.
Exchanging Public Keys Manually
If a certification authority is not available, or if one or more of the users has not registered their public keys with it, then the users need to exchange their public keys in some other manner. This can also be done if the certification authority is not considered trustworthy by one or more of the users.
When transferring keys or messages from one user to another, one of the users is designated the sending user (or sender) and the other the destination user (or receiver).
The first step is for the sender to export his public key from the CSP into a public key blob, using the CryptExportKey function. Next, the key blob must be sent to the destination user in some secure manner. Although secrecy is not necessary, both users must be confident that the integrity of the key blob remains untarnished during the transfer. (The mechanics of how this is done are completely independent of the CryptoAPI.)
Public key blobs are not encrypted. Thus, it would not be difficult for the sending application to convert the key blob to a human-readable format, so that the sender could read the public key to the receiver over the phone. Furthermore, it would not be difficult for the receiving application to reconstruct the public key blob.
Once the receiver has received the key blob data from the sender, it imports the key blob into its own CSP. This is done using the CryptImportKey function.
Exchanging Session Keys
To send another user an encrypted message, it becomes necessary to send that user the session key that was used to perform the encryption. There are two ways this can be approached:
The sending user can create a random session key, encrypt it using the receiver's public key, and send the encrypted key (key blob) to the receiver. The sender can then send messages encrypted with this session key to the receiver. This approach is discussed in the following section.
The sending and receiving users can mutually agree on a session key by exchanging several messages back and forth. The users can then use this session key to send encrypted messages back and forth. The "Sample Three-Phase Exchange Protocol" section describes a sample three-phase key exchange protocol that can be used for this purpose. Designing one of these protocols (and getting it right!) is fairly difficult and should only be attempted by an experienced cryptographer.
Note
This section assumes that the users (or CryptoAPI clients) already possess their own set of public/private key pairs and have also obtained each other's public keys.
Sending an Encrypted Session Key
The easiest way to send encrypted messages to another user is to send the message (encrypted with a random session key) along with the session key (encrypted with the receiver's exchange public key). These are the steps for sending an encrypted session key:
1. Create a random session key using the CryptGenKey function.
2. Encrypt the message using the session key. (This procedure is discussed in the section "Encrypting and Decrypting Data.")
3. Export the session key into a key blob with the CryptExportKey function, specifying that the key be encrypted with the destination user's key exchange public key.
4. Send both the encrypted message and the encrypted key blob to the destination user.
5. The destination user should then import the key blob into his or her CSP using the CryptImportKey function. This will automatically decrypt the session key, provided the destination user's key exchange public key was specified in step 3.
6. The destination user can then decrypt the message using the session key, following the procedure discussed in Chapter 6.
The following illustration shows how to send an encrypted message using this procedure:
Sending an Encrypted Message with Key Blob
This approach is vulnerable to at least one common form of attack. An eavesdropper can acquire copies of one of more encrypted messages and the encrypted keys. Then, at some later time, the eavesdropper can send one of these messages to the receiver and the receiver will have no way of knowing the message did not come directly from the original sender. This risk can be reduced by timestamping all messages or by using serial numbers. Using a three-phase key exchange protocol will eliminate this problem entirely. See the "Sample Three-Phase Exchange Protocol" section.
Key Blobs Explained
Key blobs provide a way to store keys outside of the CSP. Chapter 4 stated that keys are always kept inside of the provider for safekeeping and applications are only allowed access to the key through a handle. Well, key blobs are the one exception to this rule.
Key blobs are created by exporting an existing key out of the provider, using the CryptExportKey function. Later, the key blob can be imported into a provider (often a different provider on a different computer), using the CryptImportKey function. This will create a key in the provider that is a duplicate of the one that was exported. In this way, key blobs are used as the medium for securely transferring keys from one provider to another.
Note
Private keys can be neither exported nor imported - they never leave the safety of the CSP module. When the handle to a public/private key pair is passed into CryptExportKey, only the public portion is placed into the key blob.
Key blobs consist of a standard header followed by data that represents the key itself. If the key blob contains a session key, then this data is always kept encrypted. Applications generally do not access the internals of key blobs but, instead, treat them as opaque objects. This opaque quality was the inspiration for the name of "key blob."
Key blobs are personalized in that they are encrypted with the key exchange public key of the intended recipient. This makes them fairly secure. To make them tamperproof, keys are sometimes signed with the key exchange private key of the originating user.
There are currently two types of key blobs:
Simple key blobs
Public key blobs
Simple Key Blobs
A simple key blob (SIMPLEBLOB) is a session key encrypted with the public key exchange key of the destination user. This key blob type is used when storing a session key or transmitting a session key to another user.
Public Key Blobs
A public key blob (PUBLICKEYBLOB) contains the public key portion of a public/private key pair. Unlike simple key blobs, these are not encrypted.
When communicating with someone who is not using CryptoAPI, your application may need to build one of these key blobs manually so the other user's public keys can be imported into your CSP. In addition, it is not unheardof for an application to display the value of a public key in plaintext form so the user can validate it by hand. (The format of public key blobs is fully documented in the Microsoft Cryptographic Service Provider Programmer's Guide.)
Sample Three-Phase Exchange Protocol
To generate an authenticated and encrypted connection between two parties on a nonsecure network, a set of messages can be exchanged which negotiate an encryption key between them. Both parties contribute a portion of the data that the negotiated session key is derived from. This protocol ensures that both the parties are currently active and are sending messages directly to each other.
Note
This section assumes that both parties involved already possess their own set of public/private key pairs and that they have also obtained each other's public keys.
It is further assumed that the parties have already exchanged human-readable user names. This is generally done at the same time the public keys are exchanged, since the user name is included as part of each certificate. When necessary, the public key data can be used as the user name, although this is not recommended. All that really matters, though, is that each party's user name be tightly bound to their public key and that both parties agree on what their respective user names are.
Overview of the Sample Protocol
This protocol provides a standard way for two parties to create an authenticated, real-time connection between themselves. The end result of this protocol is a session key that is shared by both of the parties involved. This protocol is known as a three-phase protocol because it requires that the two parties exchange three packets of data in the process of creating the shared session key. This is shown in the following illustration, reading from top to bottom.
Sample Three-phase Key Exchange Protocol
A variety of key exchange protocols can be implemented using CryptoAPI. The protocol discussed here is just one of many possibilities. However, using this particular protocol will tend to increase your application's potential interoperability.
Following is a description of this protocol. One of the parties is arbitrarily designated the sending user (or sender) and the other the destination user (or receiver).
Phase 1
In Phase 1, the sender creates a random session key, to be known as "session key A", using the CryptGenKey function. The sender then uses CryptExportKey to export this key into a simple key blob, specifying that the receiver's exchange public key be used to encrypt the key blob. This key blob is then sent to the receiver.
The receiver accepts the key blob from the sender and imports it into its CSP, using the CryptImportKey function. This function returns a handle to session key A to the receiver.
Phase 2
In Phase 2, the receiver then creates a random session key of its own, to be known as "session key B." The receiver exports this key into a key blob and transmits it to the sender.
The receiver then builds up a hash value containing session key A, the receiver's name, session key B, the sender's name, and the text "phase 2." This hash value is then sent to the sending user. (The details of this process are discussed in this chapter under "Receiver Code Example.")
The data must be hashed in the standard sequence, so the sender will be able to properly validate it. The data formats used by the sender and receiver must also match, although a standard format is not specified here.
The sending user accepts the "session key B" key blob from the receiver, and imports it into its CSP. The hash value is also received.
The sending user then validates the receiver's hash value by creating a hash of its own containing the same data, and comparing the two hash values. If the hash values do not match, then either the destination user has not been forthright, or someone else is tampering with the data between the two parties. In either case, the protocol should be terminated and the communication link severed.
If the two hash value do match, this tells the sender that the destination user is presently online and in real-time communication. This is primarily because the hash value contains session key A, which was sent out encrypted with the destination user's public key. Only the real destination user could have decrypted the session key and built the hash value. Including the human-readable user names in the hash makes it possible to involve the users in the process as an additional check.
Phase 3
In Phase 3, the sender builds up a hash value containing session key B, the sender's name, the receiver's name, and the text "phase 3." This hash value is then sent to the destination user. (The details of this process are discussed in this chapter under "Sender Code Example.")
The destination user accepts the hash value from the sender and validates it by creating a hash of its own and comparing the two hash values. If the hash values do not match, then the protocol should be terminated and the communication link severed.
If the two hash value do match, this tells the receiver that the sending user is presently online and in real-time communication. This is primarily because the hash value contains session key B, which was sent out encrypted with the sending user's public key. Only the real sending user could have decrypted the session key and built the hash value.
Protocol Conclusion
Once the two parties have exchanged session keys and hash values and the hash values have been properly validated, the protocol is complete. Each party can now independently create a shared session key that can be used to send encrypted messages to each other.
To create the shared session key, each party must create a hash object with CryptCreateHash, add session key A and session key B to the hash with CryptHashSessionKey, and then derive the key using CryptDeriveKey. This procedure is shown in detail in the code examples.
Sender Code Example
This section shows the code needed on the sending user side to implement the three-phase key exchange protocol. The details of the communication between the sending user and the destination user are not shown, because these will be different for each implementation.
For purposes of readability, this example and the following one blatantly avoid good programming practice in two major ways:
No error checking is shown. A working program should always check the returned error codes and perform some appropriate action when an error is encountered.
Fixed-length buffers are used to store key blobs and hash values. In practice, these buffers should be allocated dynamically, because this data will vary in size depending on the CSP used.
#include <wincrypt.h> HCRYPTPROV hProv = 0; #define NAME_SIZE 256 BYTE pbDestName[NAME_SIZE]; DWORD dwDestNameLen; BYTE pbSendName[NAME_SIZE]; DWORD dwSendNameLen; HCRYPTKEY hDestPubKey = 0; HCRYPTKEY hSharedKey = 0; HCRYPTKEY hKeyA = 0; HCRYPTKEY hKeyB = 0; #define BLOB_SIZE 256 BYTE pbKeyBlob[BLOB_SIZE]; DWORD dwBlobLen; #define HASH_SIZE 256 BYTE pbHash[HASH_SIZE]; DWORD dwHashLen; BYTE pbDestHash[HASH_SIZE]; DWORD dwDestHashLen; HCRYPTHASH hHash = 0; // Get handle to the default provider. CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0); // Obtain the destination user's exchange public key. Import it into // the CSP and place a handle to it in 'hDestPubKey'. ... CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hDestPubKey); // Obtain the destination user's name. This is usually done at the // same time as the public key was obtained. Place this in // 'pbDestName' and set 'dwDestNameLen' to the number of bytes in // the name. ... // Place the sending user's name in 'pbSendName' and set // 'dwSendNameLen' to the number of bytes in it. ... // Create a random session key (session key A). Because this key will // be used solely for key exchange and not encryption, it // does not matter which algorithm you specify here. CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKeyA); // Export session key A into a simple key blob. dwBlobLen = BLOB_SIZE; CryptExportKey(hKeyA, hDestPubKey, SIMPLEBLOB, 0, pbKeyBlob, &dwBlobLen); // Send key blob containing session key A to the destination user. ... // Wait for the destination user to respond. ... // Receive a key blob containing session key B from the destination // user and place it in 'pbKeyBlob'. Set 'dwBlobLen' to the number // of bytes in the key blob. ... // Receive a hash value from the destination user and place it in // 'pbHashValue'. Set 'dwHashLen' to the number of bytes in the hash // value. ... // Import the key blob into the CSP. CryptImportKey(hProv, pbKeyBlob, dwBlobLen, 0, 0, &hKeyB); // // Verify hash value received from the destination user. // // Create hash object. CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash); // Add session key A to hash. CryptHashSessionKey(hHash, hKeyA, 0); // Add destination user's name to hash. CryptHashData(hHash, pbDestName, dwDestNameLen, 0); // Add session key B to hash. CryptHashSessionKey(hHash, hKeyB, 0); // Add sending user's name to hash. CryptHashData(hHash, pbSendName, dwSendNameLen, 0); // Add "phase 2" text to hash. CryptHashData(hHash, "phase 3", 7, 0); // Complete the hash computation and retrieve the hash value. dwHashLen = HASH_SIZE; CryptGetHashParam(hHash, HP_HASHVALUE, pbHash, &dwHashLen, 0); // Destroy the hash object. CryptDestroyHash(hHash); // // Compare the hash value received from the destination user with // the hash value that we just computed. If they do not match, then // terminate the protocol. // if(dwHashLen!=dwDestHashLen || memcmp(pbHash, pbDestHash, dwHashLen)) { printf("Key exchange protocol failed in phase 2!\n"); printf("Aborting protocol!\n"); return; } // // Compute hash to be sent to the destination user. // // Create hash object. CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash); // Add session key B to hash. CryptHashSessionKey(hHash, hKeyB, 0); // Add sending user's name to hash. CryptHashData(hHash, pbSendName, dwSendNameLen, 0); // Add destination user's name to hash. CryptHashData(hHash, pbDestName, dwDestNameLen, 0); // Add "phase 3" text to hash. CryptHashData(hHash, "phase 3", 7, 0); // Complete the hash computation and retrieve the hash value. dwHashLen = HASH_SIZE; CryptGetHashParam(hHash, HP_HASHVALUE, pbHash, &dwHashLen, 0); // Destroy the hash object. CryptDestroyHash(hHash); // Send the hash value to the destination user. ... // // Create a shared session key to be used by both users for // exchanging encrypted messages. Both users must agree on the // algorithm and parameters that this key is to use. // // Create hash object. CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash); // Add session key A to hash. CryptHashSessionKey(hHash, hKeyA, 0); // Add session key B to hash. CryptHashSessionKey(hHash, hKeyB, 0); // Complete the hash computation and derive a session key from it. // The CRYPT_EXPORTABLE flag is not specified here because the key // usually is not exported out of the CSP. CryptDeriveKey(hProv, CALG_RC4, hHash, 0, &hSharedKey); // Destroy the hash object. CryptDestroyHash(hHash); // // Use the shared key to send encrypted messages to the other user. // ... // Destroy session key. CryptDestroyKey(hSharedKey); // Release provider handle. CryptReleaseContext(hProv, 0);
Receiver Code Example
This section illustrates the code needed on the destination user side to implement the three-phase key exchange protocol. The details of the communication between sending user and the destination user are not shown, because these will be different for each implementation.
#include <wincrypt.h> HCRYPTPROV hProv = 0; #define NAME_SIZE 256 BYTE pbDestName[NAME_SIZE]; DWORD dwDestNameLen; BYTE pbSendName[NAME_SIZE]; DWORD dwSendNameLen; HCRYPTKEY hSendPubKey = 0; HCRYPTKEY hSharedKey = 0; HCRYPTKEY hKeyA = 0; HCRYPTKEY hKeyB = 0; #define BLOB_SIZE 256 BYTE pbKeyBlob[BLOB_SIZE]; DWORD dwBlobLen; #define HASH_SIZE 256 BYTE pbHash[HASH_SIZE]; DWORD dwHashLen; BYTE pbSendHash[HASH_SIZE]; DWORD dwSendHashLen; HCRYPTHASH hHash = 0; // Get handle to the default provider. CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0); // Obtain the sending user's exchange public key. Import it into the // CSP and place a handle to it in 'hSendPubKey'. ... // Obtain the sending user's name. This is usually done at the // same time the public key was obtained. Place this in // 'pbSendName' and set 'dwSendNameLen' to the number of bytes in // the name. ... // Place the destination user's name in 'pbDestName' and set // 'dwDestNameLen' to the number of bytes in the name. ... // Receive a key blob containing session key A from the sending user // and place it in 'pbKeyBlob'. Set 'dwBlobLen' to the number of // bytes in the key blob. ... // Import the key blob into the CSP. CryptImportKey(hProv, pbKeyBlob, dwBlobLen, 0, 0, &hKeyA); // Create a random session key (session key B). Because this key is // going to be used solely for key exchange and not encryption, it // does not matter which algorithm you specify here. CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKeyB); // Export session key B into a simple key blob. dwBlobLen = BLOB_SIZE; CryptExportKey(hKeyB, hSendPubKey, SIMPLEBLOB, 0, pbKeyBlob, &dwBlobLen); // Transmit key blob containing session key B to the sending user. ... // // Compute hash value and transmit it to the sending user. // // Create hash object. CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash); // Add session key A to hash. CryptHashSessionKey(hHash, hKeyA, 0); // Add destination user's name to hash. CryptHashData(hHash, pbDestName, dwDestNameLen, 0); // Add session key B to hash. CryptHashSessionKey(hHash, hKeyB, 0); // Add sending user name to hash. CryptHashData(hHash, pbSendName, dwSendNameLen, 0); // Add "phase 2" text to hash. CryptHashData(hHash, "phase 2", 7, 0); // Complete the hash computation and retrieve the hash value. dwHashLen = HASH_SIZE; CryptGetHashParam(hHash, HP_HASHVALUE, pbHash, &dwHashLen, 0); // Destroy the hash object. CryptDestroyHash(hHash); // Transmit the hash value to the sending user. ... // Wait for the sending user to respond. ... // Receive a hash value from the sending user and place it in // 'pbSendHashValue'. Set 'dwSendHashLen' to the number of bytes in // the hash value. ... // // Verify hash value received from the sending user. // // Create hash object. CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash); // Add session key B to hash. CryptHashSessionKey(hHash, hKeyB, 0); // Add sending user's name to hash. CryptHashData(hHash, pbSendName, dwSendNameLen, 0); // Add destination user's name to hash. CryptHashData(hHash, pbDestName, dwDestNameLen, 0); // Add "phase 3" text to hash. CryptHashData(hHash, "phase 3", 7, 0); // Complete the hash computation and retrieve the hash value. dwHashLen = HASH_SIZE; CryptGetHashParam(hHash, HP_HASHVALUE, pbHash, &dwHashLen, 0); // Destroy the hash object. CryptDestroyHash(hHash)); // // Compare the hash value received from the sending user with the // hash value that we just computed. If they do not match, then // terminate the protocol. // if(dwHashLen!=dwSendHashLen || memcmp(pbHash, pbSendHash, dwHashLen)) { printf("Key exchange protocol failed in phase 3!\n"); printf("Aborting protocol!\n"); return; } // // Create a shared session key to be used by the two users for // exchanging encrypted messages. Both users must agree on the // algorithm and parameters that this key is to use. // // Create hash object. CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash); // Add session key A to hash. CryptHashSessionKey(hHash, hKeyA, 0); // Add session key B to hash. CryptHashSessionKey(hHash, hKeyB, 0); // Complete the hash computation and derive a session key from it. // The CRYPT_EXPORTABLE flag is not specified here because the key // is not generally exported out of the CSP. CryptDeriveKey(hProv, CALG_RC4, hHash, 0, &hSharedKey); // Destroy the hash object. CryptDestroyHash(hHash); // // Use the shared key to send encrypted messages to the other user. // ... // Destroy session key. CryptDestroyKey(hSharedKey); // Destroy handle to sending user's public key. CryptDestroyKey(hSharedKey); // Release provider handle. CryptReleaseContext(hProv, 0);
CHAPTER 6
Encrypting and Decrypting Data
Encryption is the process in which data (plaintext) is translated into something that appears to be random and meaningless (ciphertext). Decryption is the process in which the ciphertext is converted back to plaintext.
A symmetric encryption key (also known here as a session key) is used during both the encryption and decryption processes. In order to decrypt a particular piece of ciphertext, you must possess the key that was used to encrypt the data. Essentially, a session key merely consists of a random number, of approximately 40 to 2000 bits in length. The longer the key that is used, the more difficult it is to decrypt a piece of ciphertext without possessing the key.
The goal of every encryption algorithm is to make it as difficult as possible to decrypt the generated ciphertext without using the key. If a really good encryption algorithm is used, then there is no technique significantly better than methodically trying every possible key. Even for a key size of just 40 bits, this works out to 240 (just over 1 trillion) possible keys.
It is surprisingly difficult to determine just how good an encryption algorithm is. Algorithms that look promising sometimes turn out to be very easy to break, given the proper attack. When selecting an encryption algorithm, it is probably a good idea to choose one that has been around for a while, and successfully resisted all attacks thus far.
Introduction to Encryption Techniques
CryptoAPI can be used by applications to easily encrypt and decrypt messages and files. This section discusses the various options available for encrypting data. For a hands-on description of how to encrypt data using CryptoAPI, see "Encrypting Files and Messages" in this chapter.
The encryption algorithms available to an application depend on the cryptographic service provider (CSP) being used. However, most CSPs share most of the attributes discussed here. All data encryption using CryptoAPI is performed with a symmetric algorithm, regardless of which CSP is installed.
Stream Ciphers
Stream cipher algorithms encrypt data one bit at a time. A stream of plaintext bits flows in one side, and a stream of encrypted ciphertext flows out the other. At least, this is the way it works mathematically; in practice, data is always encrypted in byte units.
Stream ciphers are not generally considered as secure as block ciphers, although this will vary depending on the particular algorithm. On the other hand, they do tend to execute faster in software. Ciphertext encrypted with stream ciphers is always the same size as the original plaintext.
Error propagation is usually less when stream ciphers are used. If a bit of ciphertext gets garbled, many stream cipher algorithms will produce only a single bit of garbled plaintext. When a block cipher is used and a ciphertext bit is garbled, at minimum an entire block's worth of plaintext will be garbled. This can be good or bad, depending on the application.
The only stream cipher provided with the Microsoft RSA Base Provider is the RC4 stream cipher.
Block Ciphers
Block cipher algorithms encrypt data in block units, rather than a single bit at a time. The most common block size is 64 bits.
Because each block is heavily "processed," block ciphers are generally considered more secure than stream ciphers. However, block cipher algorithms tend to execute quite a bit slower.
All that the basic block encryption algorithm specifies is how to get a block of ciphertext from a block of plaintext and vice versa. All the other implementation details (for example, padding, initialization vectors, and cipher modes) are specified independently of the algorithm. These options are discussed in the next few sections.
The only block cipher provided with the Microsoft RSA Base Provider is the RC2 block cipher. This algorithm has a block size of 64 bits.
Padding
Most plaintext messages will not consist of an even number of blocks. Often, the last block is short, making it necessary to add a padding string. For example, if the block length is 64 bits and the last block contains only 40 bits, then 24 bits of padding must be added.
This padding string can consist of all zeros, alternating zeros and ones, or some other pattern. Some encryption standards specify a particular padding scheme, such as the one described in the next section.
Applications using CryptoAPI need not add padding to their plaintext before it is encrypted, nor do they have to remove it after decrypting. This is all handled automatically by CryptoAPI.
PKCS Padding
This padding scheme is defined by RSA Data Security, Inc. and is documented in Public-Key Cryptography Standards (PKCS), PKCS #5, section 6.2.
When this method is used, a padding string is always added, even if the plaintext message divides evenly into blocks. The padding string consists of a sequence of bytes, each of which is equal to the total number of bytes in the padding string. If 24 bits of padding need to be added, then the padding string is "03 03 03." If 64 bits of padding needs to be added, then the string is "08 08 08 08 08 08 08 08."
Cipher Modes
When a block cipher is used, any one of the following cipher modes can be specified via the CryptSetKeyParam function. If the application does not explicitly specify one of these modes, then the cipher block chaining (CBC) cipher mode is used.
Electronic Codebook (ECB)
When this cipher mode is used, each block is encrypted individually. No feedback is used. This means any blocks of plaintext that are identical and are either in the same message, or in a different message that is encrypted with the same key, will be transformed into identical ciphertext blocks.
If the plaintext to be encrypted contains substantial repetition, then it is feasible for the ciphertext to be broken one block at a time. Furthermore, it is possible for an unscrupulous person to substitute and exchange individual blocks without detection.
Initialization vectors cannot be used with this cipher mode.
If a single bit of the ciphertext block is garbled, then the entire corresponding plaintext block will also be garbled.
Cipher Block Chaining (CBC)
This cipher mode introduces feedback. Before each plaintext block is encrypted, it is XOR'ed with the ciphertext of the previous block. This ensures that even if the plaintext contains many identical blocks, they will each encrypt to a different ciphertext block.
The initialization vector is XOR'ed with the first plaintext block before the block is encrypted.
As with the Codebook cipher mode, if a single bit of the ciphertext block is garbled, then the corresponding plaintext block will also be garbled. In addition, a bit in the subsequent plaintext block (in the same position as the original garbled bit) will be garbled. Synchronization errors are fatal. If there are extra or missing bytes in the ciphertext, the plaintext will be garbled from that point on.
When the Microsoft RSA Base Provider is used, this is the default cipher mode.
Cipher Feedback Mode (CFB)
The cipher feedback mode lets you process small increments of plaintext into ciphertext, instead of processing an entire block at a time. This can be is useful, for example, when encrypting a stream of data that originates at a keyboard. Each keystroke can be encrypted and transmitted without the need to wait for an entire block to be typed.
This mode uses a shift register which is one block size in length and divided up into sections. For example, if the block size is 64 bits with 8 bits processed at a time, then the shift register would be divided up into 8 sections.
This is the procedure for each encryption cycle:
1. The block in the shift register is encrypted normally.
2. The leftmost 8 bits in the encrypted shift register are XOR'ed with the next 8 bits of plaintext and sent off as 8 bits of ciphertext.
3. The shift register is shifted 8 bits to the left.
4. The 8 bits of ciphertext generated in step 2 is placed in the rightmost 8 bits of the shift register.
In CryptoAPI, the number of bits processed at one time is specified by setting the encryption key's KP_MODE_BITS parameter using the CryptSetKeyParam function. This parameter typically defaults to 8.
Depending on the value of the KP_MODE_BITS parameter, this cipher mode is substantially slower than the Cipher Block Chaining mode. For example, if the block size is 64 bits with 8 bits are processed at a time, this cipher mode is 64/8 or 8 times slower.
Before the encryption process begins, the shift register is filled with the initialization vector.
If a bit in the cipher text is garbled, one plaintext bit is garbled and the shift register is corrupted. This results in the next several plaintext blocks being garbled until the bad bit is shifted out of the shift register. In the preceding example, 9 bytes of plaintext would be garbled. This is the same amount of error propagation as with the Cipher Block Chaining mode. Synchronization errors are not fatal, provided that the slip is a multiple of KP_MODE_BITS. Thus, if KP_MODE_BITS is 8 and there are extra or missing bytes from the ciphertext, then 9 bytes of plaintext are garbled and the plaintext will have the same number of extra or missing bytes.
Output Feedback Mode (OFB)
This mode is similar to the cipher feedback mode. The only difference between the two modes is how the shift register is filled.
The output feedback (OFB) cipher mode uses the following encryption cycle:
1. The block in the shift register is encrypted normally.
2. The leftmost 8 bits in the encrypted shift register are XOR'ed with the next 8 bits of plaintext and sent off as 8 bits of ciphertext.
3. The shift register is shifted 8 bits to the left.
4. The leftmost 8 bits of the encrypted shift register used in step 2 is placed in the rightmost 8 bits of the shift register.
As with the Cipher Feedback mode, the shift register is filled with the initialization vector before the encryption process starts.
If a bit in the cipher text is garbled, the corresponding bit of plaintext will also be garbled. This is much better than the Cipher Feedback mode. However, synchronization errors are fatal. If there are extra or missing bits from the ciphertext, then the plaintext will be garbled from that point on.
Note
According to Gait (see reference below), the OFB block cipher mode has a weakness when the number of bits fed back is different than the block size. It is thus recommended that the KP_MODE_BITS parameter be set to the block size when this cipher mode is used.
*J. Gait, "A New Nonlinear Pseudorandom Number Generator," IEEE Transactions on Software Engineering, v. SE-3, n. 5, Sep 1977, pp. 359-363.
Initialization Vectors
An initialization vector is a random number, usually the same number of bits as the block size, that is used as a starting point when encrypting a set of data. Initialization vectors are only used with those cipher modes that make use of feedback. This ensures that the effect of the initialization vector is propagated throughout the entire plaintext message being encrypted.
If initialization vectors are not used, then when two identical plaintext messages are encrypted with the same key, two identical ciphertext messages are generated. However, if each plaintext message is encrypted with a different initialization vector, the ciphertext messages generated are completely different.
You should always encrypt each message with a different initialization vector, particularly when the messages contain a large amount of duplication.
Applications using CryptoAPI are responsible for transmitting the initialization vector along with the encrypted message. There is no need to encrypt this vector.
Salt Values
Salt values make up a portion of many session keys, as shown.
Structure of a session key.
As with the key bits, the salt bits also consist of random data. The difference is that the key bits must be kept secret at all costs, while the salt values are made public. When exchanging keys using the CryptoAPI, the key bits are transmitted inside of encrypted key blobs. The salt bits, on the other hand, are transmitted in plaintext form.
The size of the salt values will vary, depending on the CSP used. For example, the Microsoft RSA Base Provider uses salt values of 88 bits and key values of 40 bits, for a total key size of 128 bits. Even though the salt bits make up part of each encryption key, they are usually ignored when discussing keys and key sizes. That is, when talking about Microsoft RSA Base Provider encryption keys, we refer to them as 40 bit keys.
Salt values are most useful when transmitting or storing large amounts of nearly identical packets using the same encryption key. Normally, two identical packets would encrypt into two identical ciphertext packets. However, this would indicate to an eavesdropper that the packets are identical and, thus, could be attacked simultaneously. But, if the salt value is changed with every packet sent, then a completely different ciphertext packet will always be generated, even if the plaintext packets are the same.
Because salt values need not be kept secret and can be transmitted in plaintext form bundled with each ciphertext packet, it is much easier to change salt values once per packet than it would be to change the key value itself.
Applications should generate salt values with the CryptGenRandom function. It is important that each salt value be completely different than the other ones, particularly when using stream ciphers.
Common Encryption Algorithms
This section briefly describes each of the encryption algorithms supplied with the Microsoft RSA Base Provider. The internal details of these algorithms are well beyond the scope of this document. Refer to "Related Documentation" at the beginning of this guide for a list of additional reading material.
The following table lists several encryption algorithms along with some performance benchmarks. This table was generated by an application using CryptoAPI on a 120-MHz, Pentium-based computer. These figures are for comparison purposes only, your mileage may vary.
Cipher Cipher Type Key Setup Time Encryption Speed (microseconds) (bytes/second) DES 64-bit block 460 1,138,519 RC2 64-bit block 40 286,888 RC4 stream 151 2,377,723
RC2 Block Cipher
The RC2 block cipher algorithm was developed by RSA Data Security, Inc. The details of this algorithm have not been published.
RC2 is a variable-key-length cipher. However, when using CryptoAPI with the Microsoft RSA Base Provider, the key length is hard-coded to 40 bits. The block size is fixed at 64 bits.
RC4 Stream Cipher
The RC4 stream cipher was developed by RSA Data Security, Inc. The details of this algorithm have not been published.
RC4 is a variable-key-length cipher. However, when using CryptoAPI with the Microsoft RSA Base Provider, the key length is hard-coded to 40 bits.
RSA Public-Key Cipher
The RSA public-key cipher was developed by (and named after) Ron Rivest, Adi Shamir, and Leonard Adleman, in the late 1970's. This algorithm is very well known; you can read about its internal details in any book on cryptography.
RSA is used by many CSPs to encrypt/decrypt keys and to generate/verify digital signatures. This algorithm is used when operations are performed using either the key exchange or digital signature key pair. When using CryptoAPI, this algorithm cannot be used to encrypt bulk data.
RSA is a variable-key-length cipher. However, when using CryptoAPI with the Microsoft RSA Base Provider, the key length is hard-coded to 512 bits.
Encrypting Files and Messages
To encrypt a file so only the current user can access its data, the file is bulk encrypted with a symmetric cipher. The key to this cipher is then kept in an access block (key blob) that can only be opened with the user's private key. Note that this technique also works for encrypting messages for specific recipients.
Encrypting Messages Using CryptoAPI
To encrypt a message, a session key must first be generated using the CryptGenKey function. Making this call generates a random key and returns a handle so the key can be used to encrypt and decrypt data. The encryption algorithm to use is also specified at this point. Because the CryptoAPI does not permit applications to use public-key algorithms to encrypt bulk data, you should specify a symmetric algorithm such as RC2 or RC4, with the CryptGenKey call.
Alternatively, if an application needs to encrypt the message in such a way that anyone with a given password can decrypt the data, the CryptDeriveKey function should be used to transform the password into a key suitable for encryption. Note that, in this case, this function is called instead of the CryptGenKey function and the subsequent CryptExportKey calls are not needed.
Once the key has been generated, extra cryptographic properties of the key can be set with the CryptSetKeyParam function. For example, this function allows different sections of the file to be encrypted with different key salts and provides a way to change the cipher mode or initialization vector of the key. These parameters can be used to make the encryption conform with a particular data encryption standard.
Encrypt the data in the file with the CryptEncrypt function. The CryptEncrypt function takes a session key, which was generated in the previous step, and encrypts a buffer of data. Note that as the data is encrypted, the data may be slightly expanded by the encryption algorithm. The application is responsible for remembering the length of the encrypted data so the proper length can later be given to the CryptDecrypt function.
To allow the current user to decrypt the data in the future, the CryptExportKey function is used to save the decryption key in an encrypted form (a key blob) that can only be decrypted with the user's private key. This function requires the user's key exchange public key for this purpose, which can be obtained by using the CryptGetUserKey function. The CryptExportKey function will return a key blob that must be stored by the application for use in decrypting the file.
Note that if the application has certificates (or public keys) for other users, it can permit other users to decrypt the file by performing CryptExportKey calls for each user it wants to give access. The returned key blobs must be stored by the application, as in the previous step.
Structure of an Encrypted File
There are a number of standard formats for encrypted files and messages. These are designed to make it easier for different applications to communicate. An explanation of these formats falls outside the scope of this document. Refer to "Related Documentation" at the beginning of this guide for a list of additional reading material.
Once a file or message has been encrypted, the following data must be stored by the application and is usually kept bundled together. This is the data:
The encrypted data. When a block cipher is used, the data is padded out to a multiple of the cipher's block size. Padding is often added even when the original message is already an even multiple. When a stream cipher is used, the encrypted data is generally the same size as the original plaintext.
One or more key blobs, each containing the session key used to encrypt the message. Each of these key blobs is encrypted with the key exchange public key of a user who is to later decrypt the data. Note that these are not stored if the key was derived from a password. Instead, when it is time to decrypt the message, the session key is recreated from the password. The password itself must be remembered by the user, of course.
Any salt values that were specified as the data was being encrypted. When the data is decrypted, these values will have to be specified (using the CryptSetKeyParam function) in the same manner as when the data was encrypted.
Any initialization vectors that were specified as the data was being encrypted. These values are handled in much the same way as the salts.
All parameters that were specified with the CryptSetKeyParam function as the message was being encrypted must also be specified as the message is decrypted. It may be appropriate to store some of these parameters with the encrypted message as well.
Encryption Example
This example reads data from a text file (test1.txt), encrypts it using the RC2 block cipher, and writes out the encrypted data to another file (test1.xxx). A random session key is generated to perform the encryption and is stored to the output file along with the encrypted data. Note that this session key is encrypted with our own public key exchange key by the CryptExportKey function.
#include <wincrypt.h> FILE *hSource = NULL; FILE *hDest = NULL; int eof = 0; HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; HCRYPTKEY hXchgKey = 0; #define BLOCK_SIZE 160 #define BUFFER_SIZE (BLOCK_SIZE+16) // Give buffer 16 bytes of extra // room for padding, etc. BYTE pbBuffer[BUFFER_SIZE]; DWORD dwCount; BYTE *pbKeyBlob = NULL; DWORD dwBlobLen; // Open source file. if((hSource=fopen("test1.txt","rb"))==NULL) { printf("Error opening source file!\n"); goto done; } // Open destination file. if((hDest=fopen("test1.xxx","wb"))==NULL) { printf("Error opening destination file!\n"); goto done; } // Get handle to the default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Get handle to key exchange key. if(!CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hXchgKey)) { printf("Error %x during CryptGetUserKey!\n", GetLastError()); goto done; } // Create a random block cipher session key. if(!CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKey)) { printf("Error %x during CryptGenKey!\n", GetLastError()); goto done; } // Determine size of key blob and allocate memory. if(!CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, NULL, &dwBlobLen)) { printf("Error %x computing blob length!\n", GetLastError()); goto done; } if((pbKeyBlob = malloc(dwBlobLen)) == NULL) { printf("Out of memory!\n"); goto done; } // Export key into a simple key blob. if(!CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, pbKeyBlob, &dwBlobLen)) { printf("Error %x during CryptExportKey!\n", GetLastError()); free(pbKeyBlob); goto done; } // Write size of key blob to destination file. fwrite(&dwBlobLen, sizeof(DWORD), 1, hDest); if(ferror(hDest)) { printf("Error writing header!\n"); free(pbKeyBlob); goto done; } // Write key blob to destination file. fwrite(pbKeyBlob, 1, dwBlobLen, hDest); if(ferror(hDest)) { printf("Error writing header!\n"); free(pbKeyBlob); goto done; } // Free memory. free(pbKeyBlob); // Encrypt source file and write to destination file. do { // Read up to BLOCK_SIZE bytes from source file. dwCount = fread(pbBuffer, 1, BLOCK_SIZE, hSource); if(ferror(hSource)) { printf("Error reading data!\n"); goto done; } eof=feof(hSource); // Encrypt data if(!CryptEncrypt(hKey, 0, eof, 0, pbBuffer, &dwCount, BUFFER_SIZE)) { printf("Error %x during CryptEncrypt!\n", GetLastError()); goto done; } // Write data to destination file. fwrite(pbBuffer, 1, dwCount, hDest); if(ferror(hDest)) { printf("Error writing data!\n"); goto done; } } while(!feof(hSource)); done: // Destroy session key. if(hKey != 0) CryptDestroyKey(hKey); // Destroy key exchange key. if(hXchgKey != 0) CryptDestroyKey(hXchgKey); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0); // Close source file. if(hSource != NULL) fclose(hSource); // Close destination file. if(hDest != NULL) fclose(hDest);
Decrypting Messages Using CryptoAPI
If a message was encrypted for a particular user, then the CryptGenKey function was used to create a random session key, before the encryption was performed. This means that before the message can be decrypted, the key blob containing the session key needs to be imported into the CSP with the CryptImportKey function. This function will use your key exchange private key to decrypt the key blob. This means that the key blob must have been originally created using the matching key exchange public key.
If the message was encrypted so that any password holder can access the data, the CryptImportKey function is not used. Instead, you create the decryption session key with the CryptDeriveKey function. You will also need to supply the function with the password (or other access token).
The session key's parameters need to be configured in the same way as when the encryption was performed. These parameters can be specified using the CryptSetKeyParam function. For example, if the salt value was changed one or more times during the encryption process, then it must also be changed during the decryption process in exactly the same manner.
The message is decrypted using the CryptDecrypt function. If the message is too large to fit comfortably in memory, it can be decrypted in sections, through multiple calls to CryptDecrypt.
When the decryption is complete, be sure to destroy the session key, using the CryptDestroyKey function. In addition to destroying the key, this will free up CSP resources.
Decryption Example
This example reads the encrypted data from the file created by the "Encryption Example" (test1.xxx), decrypts it using the RC2 block cipher, and writes out the plaintext data to another file (test1.txt). The session key used to perform the decryption is read from the ciphertext file.
#include <wincrypt.h> FILE *hSource = NULL; FILE *hDest = NULL; int eof = 0; HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; #define BLOCK_SIZE 160 BYTE pbBuffer[BLOCK_SIZE]; DWORD dwCount; BYTE *pbKeyBlob = NULL; DWORD dwBlobLen; // Open source file. if((hSource=fopen("test1.xxx","rb"))==NULL) { printf("Error opening source file!\n"); goto done; } // Open destination file. if((hDest=fopen("test1.txt","wb"))==NULL) { printf("Error opening destination file!\n"); goto done; } // Get handle to the default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Read key blob length from source file and allocate memory. fread(&dwBlobLen, sizeof(DWORD), 1, hSource); if(ferror(hSource) || feof(hSource)) { printf("Error reading file header!\n"); goto done; } if((pbKeyBlob = malloc(dwBlobLen)) == NULL) { printf("Out of memory!\n"); goto done; } // Read key blob from source file. fread(pbKeyBlob, 1, dwBlobLen, hSource); if(ferror(hSource) || feof(hSource)) { printf("Error reading file header!\n"); goto done; } // Import key blob into CSP. if(!CryptImportKey(hProv, pbKeyBlob, dwBlobLen, 0, 0, &hKey)) { printf("Error %x during CryptImportKey!\n", GetLastError()); goto done; } // Decrypt source file and write to destination file. do { // Read up to BLOCK_SIZE bytes from source file. dwCount = fread(pbBuffer, 1, BLOCK_SIZE, hSource); if(ferror(hSource)) { printf("Error reading data from source file!\n"); goto done; } eof=feof(hSource); // Decrypt data. if(!CryptDecrypt(hKey, 0, eof, 0, pbBuffer, &dwCount)) { printf("Error %x during CryptDecrypt!\n", GetLastError()); goto done; } // Write data to destination file. fwrite(pbBuffer, 1, dwCount, hDest); if(ferror(hDest)) { printf("Error writing data to destination file!\n"); goto done; } } while(!feof(hSource)); done: // Free memory. if(pbKeyBlob) free(pbKeyBlob); // Destroy session key. if(hKey != 0) CryptDestroyKey(hKey); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0); // Close source file. if(hSource != NULL) fclose(hSource); // Close destination file. if(hDest != NULL) fclose(hDest);
Encrypting and Decrypting Simultaneously
When encrypting or decrypting two streams of data simultaneously with the same cryptographic key, a certain amount of care must be taken. The same physical session key must not be used for both operations, because every session key contains internal state information and it will get mixed up if used for more than one operation at a time. A fairly simple solution to this problem is to make a copy of the session key. In this way, the original key can be used for one operation and the copy used for the other.
Copying a session key is done by exporting the key with CryptExportKey and then using CryptImportKey to import it back in. When the key is imported, the CSP will give the "new" key its own section of internal memory, as if it were not related at all to the original key.
The following code fragment shows how a copy of a session key can be obtained.
HCRYPTPROV hProv; // Handle to a CSP. HCRYPTKEY hKey; // Handle to a session key. HCRYPTKEY hCopyKey = 0; HCRYPTKEY hPubKey = 0; BYTE pbBlob[256]; DWORD dwBlobLen; // Get a handle to our own key exchange public key. CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hPubKey); // Export the session key into a key blob. dwBlobLen = 256; CryptExportKey(hKey, hPubKey, SIMPLEBLOB, 0, pbBlob, &dwBlobLen); // Import the session key back into the CSP. This is stored separately // from the original session key. CryptImportKey(hProv, pbBlob, dwBlobLen, 0, 0, &hCopyKey); // Use 'hKey' for one set of operations and 'hCopyKey' for the other. ...
Note that this technique should not be used with stream ciphers, as stream cipher keys should never be used more than once. Instead, separate transmit and receive keys should be used.
CHAPTER 7
Using the functions described in this section, a user can "digitally sign" a piece of data such that any other user can easily verify that the data has not been changed since it was signed. The identity of the user that signed the data can also be easily verified.
A digital signature consists of a small amount of binary data, typically less than 256 bytes. This signature can be bundled with the signed message or stored separately; this is up to the individual application.
The Microsoft RSA Base Provider creates digital signatures that conform to the RSA Public-Key Cryptography Standard (PKCS) #6.
How Digital Signatures Work
There are two steps involved in creating a digital signature from a message. The first step involves creating a hash value (also known as a message digest) from the message. This hash value is then signed, using the private key of the signer. Following is an illustration of the steps involved in creating a digital signature:
Creating a Digital Signature
To verify a signature, both the message and the signature are required. First, a hash value must be created from the message, in the same way as when the signature was created. This hash value is then verified against the signature, using the public key of the signer. If the hash value and the signature match, you can be confident that the message is indeed the one the signer originally signed and that it has not been tampered with. The following diagram illustrates the process involved in verifying a digital signature.
Verifying a Digital Signature
A hash value consists of a small amount of binary data, typically around 160 bits. This is produced using a hashing algorithm. A number of these algorithms are listed later in this section.
All hash values share the following properties, regardless of the algorithm used:
The hash value is of a fixed length, regardless of the size of the message. The message can be several kilobytes or several gigabytes, it doesn't matter. Depending on the algorithm used, the hash value length will generally be either 128 or 160 bits.
Every pair of nonidentical messages will translate into a completely different hash value, even if the two messages differ only by a single bit. Using today's technology, it is not feasible to discover a pair of messages that translate to the same hash value without breaking the hashing algorithm.
All hashing algorithms are fully deterministic. That is, each time a particular message is hashed using the same algorithm, the exact same hash value will be produced.
All hashing algorithms are one-way. Given a hash value, it is not possible to recover the original message. In fact, none of the properties of the original message can be determined given the hash value alone.
Signing and Verifying Messages
To apply a digital signature to a piece of data, a secure hash function is used to build a digest of the data (for example, a 160-bit hash value) which is then transformed with the private key of the signer. Other users can then check the authenticity of the signature by reconstructing the hash value, and checking it against the "inverse" of the digital signature data. CryptoAPI abstracts out the actual method of doing the signature, so that applications need not be aware of the signature mechanics.
Signing Data
In order to sign data, a hash object must first be created using the CryptCreateHash function. This object will accumulate the data to be signed. Next, the data is added to the hash object with the CryptHashData function.
After the last block of data is added to the hash, the CryptSignHash function is used to sign the hash. A description of the data can also be added to the hash object at this point. Once the digital signature data has been obtained, the hash object should be destroyed with the CryptDestroyHash function.
Hashes can be signed with either the signature private key or the key exchange private key. The signature key should be used when the user who owns the signature key is signing some of his or her data. The key exchange key should be used when signing data that does not directly belong to the user. The classic example of this is when the exchange key is used to sign session keys during a key exchange protocol.
Verifying Signatures
To verify a signature, a hash object must first be created using the CryptCreateHash function. This object will accumulate the data to be verified. The data is then added to the hash object with the CryptHashData function.
After the last block of data is added to the hash, the CryptVerifySignature function is used to verify the signature. The signature data, a handle to the hash object, and the description string must all be supplied to CryptVerifySignature. A handle to the key pair that was used to sign the data must also be specified.
Once the signature has been verified (or has failed the verification) the hash object should be destroyed with the CryptDestroyHash function.
Obtaining the Hash Value
To obtain the hash value, a hash object must first be created using the CryptCreateHash function. This object will accumulate the data to be verified. The data is then added to the hash object with the CryptHashData function.
After the last block of data is added to the hash, the CryptGetHashParam function is used to obtain the hash value.
Once the hash value has been obtained, the hash object should be destroyed with the CryptDestroyHash function.
Hashing and Signature Algorithms
This section lists several algorithms used to compute hashes and digital signatures. Each of these algorithms is supported by the Microsoft RSA Base Provider. However, the internal details of these algorithms are well beyond the scope of this document. Refer to "Related Documentation" at the beginning of this guide for a list of additional sources.
MD2, MD4, and MD5
The MD2, MD4, and MD5 hashing algorithms were all developed by RSA Data Security, Inc. These algorithms were developed in sequential order, with the later algorithms generally being better (more secure) than the earlier ones. All three generate 128-bit hash values. Of the three algorithms, MD5 is recommended.
These algorithms are well known and can be reviewed in detail in any reference on cryptography.
Secure Hash Algorithm (SHA)
The SHA hashing algorithm was developed by the National Institute of Standards and Technology (NIST) and by the National Security Agency (NSA). This algorithm was developed for use with DSA (Digital Signature Algorithm) or DSS (Digital Signature Standard).
This algorithm generates a 160-bit hash value.
Message Authentication Code (MAC)
Message Authentication Codes (MACs) are similar to hash values, but are computed using a session key. Because of this difference, you must possess the session key in order to recompute the hash value to verify that the base data has not changed.
The MACs implemented by the Microsoft RSA Base Provider are of the most common sort. That is, they are block cipher MACs. This method encrypts the base data with a block cipher and then uses the last encrypted block as the hash value. The encryption algorithm used to build the MAC is the one that was specified when the session key was created.
Warning
The same session key should not be used for both message encryption and MAC generation. Doing so greatly increases the risk of your messages being decoded.
CHAPTER 8
This section discusses how multiple cryptographic service providers (CSPs) can be installed on a computer and the default providers specified. The structure of the system Registry is also mentioned.
Installing a New Provider
New providers are installed by running their Setup program. This copies the CSP files to the appropriate directories and makes all needed changes to the system Registry.
Outline of CryptoAPI Registry Usage
CryptoAPI uses the system Registry to store a database of the CSPs that have been installed on the computer. Both the machine default providers and the user default providers are also recorded here.
Warning
This section is included for informational purposes only. The details of CryptoAPI's Registry usage may change at any time. Under no circumstances should an application read from or alter the Registry directly.
The following is a partial outline of the portions of the system Registry used by CryptoAPI. Some sample entries are also shown.
HKEY_LOCAL_MACHINE
SOFTWARE
Microsoft
Cryptography
Defaults
Provider
Microsoft Base Cryptographic Provider v1.0
>Image Path:REG_SZ:rsabase.dll
>Signature:REG_BINARY:<digital signature>
>Type:REG_DWORD:0x1
John's Provider
>Image Path:REG_SZ:johncsp.dll
>Signature:REG_BINARY:<digital signature>
>Type:REG_DWORD:0x2a
Provider Types
Type 001
>Name:REG_SZ:Microsoft Base Cryptographic Provider v1.0
Type 042
>Name:REG_SZ:John's Provider
HKEY_CURRENT_USER
Software
Microsoft
Cryptography
Providers
Type 001
>Name:REG_SZ:Microsoft Base Cryptographic Provider v1.0
Entries under the HKEY_LOCAL_MACHINE\...\Provider key contain information about all the CSPs that have been installed on the computer. These entries are created by the Setup program used to install a new CSP. Note that these entries are organized under subkeys whose names indicate the provider name.
Entries under the HKEY_LOCAL_MACHINE\...\Provider Types key contain the name of the machine default CSP for each provider type. These entries are also created by the Setup program used to install a new CSP. Note that these entries are organized under subkeys whose names indicate the provider type (in decimal format).
Entries under the HKEY_CURRENT_USER\...\Providers key contain the name of the current user default CSP for each provider type. These entries are created/modified by the CryptSetProvider function. Note that these entries are also organized under subkeys whose names indicate the provider type.
CHAPTER 9
The functions described in this section are used by applications to connect to and disconnect from cryptographic service providers (CSPs). The following table briefly describes each function:
Function Description CryptAcquireContext Acquires a handle to the current user's key container within a particular CSP. CryptGetProvParam Retrieves attributes of a CSP. CryptReleaseContext Releases the handle acquired by CryptAcquireContext. CryptSetProvider Specifies the user default CSP for a particular CSP type. CryptSetProvParam Specifies attributes of a CSP.
The CryptAcquireContext function is used to acquire a handle to a particular key container within a particular CSP. This returned handle can then be used to make calls to the selected CSP.
This function performs two operations. It first attempts to find a CSP with the characteristics described in the dwProvType and pszProvider parameters. If the CSP is found, then the function attempts to find a key container within the CSP matching the name specified by the pszContainer parameter.
This function can also be used to create and destroy key containers, depending on the value of the dwFlags parameter.
BOOL CRYPTFUNC CryptAcquireContext(
HCRYPTPROV *phProv, // out
LPCTSTR pszContainer, // in
LPCTSTR pszProvider, // in
DWORD dwProvType, // in
DWORD dwFlags) // in
Parameters
phProv
The address to which the function copies a handle to the CSP.
pszContainer
The key container name. This is a zero-terminated string that identifies the key container to the CSP. This name is independent of the method used to store the keys. Some CSPs will store their key containers internally (in hardware), some will use the system Registry, and others will use the file system.
If this parameter is NULL, then a default key container name will be used. For example, if the Microsoft RSA Base Provider is being used, then the current user's logon name will be used as the name of the key container. Other CSPs may also have default key containers that can be acquired in this way.
An application can obtain the name of the acquired key container at a later time by reading the PP_CONTAINER parameter from the CryptGetProvParam function.
pszProvider
The provider name. This is a zero-terminated string that specifies the CSP to be used.
If this parameter is NULL then the user default provider is used. This situation is discussed in detail in Chapter 3.
An application can obtain the name of the acquired CSP at a later time by reading the PP_NAME parameter from the CryptGetProvParam function.
dwProvType
The type of provider to acquire. The following provider types are predefined. These are discussed in detail in Chapter 3.
PROV_RSA_FULL
PROV_RSA_SIG
PROV_DSS
PROV_FORTEZZA
dwFlags
The flag values. This parameter is normally set to zero, but some applications will set one (and only one) of the following flags:
CRYPT_VERIFYCONTEXT
If this flag is set, then the application will have no access to the key container's private keys. In fact, if pszContainer is NULL and no default key container is present, the application will have no access to a key container at all.
This option is intended to be used by applications whose only cryptographic need is to verify digital signatures. The only operations normally needed in this case are public key import, hashing, and signature verification.
When CryptAcquireContext is called, many CSPs will require input from the owning user before granting access to the private keys in the key container. For example, the private keys may be encrypted, requiring a password from the user before they can be used. However, if the CRYPT_VERIFYCONTEXT flag is specified, access to the private keys is not required and the user interface can be bypassed.
CRYPT_NEWKEYSET
If this flag is set, then a new key container will be created with the name specified by pszContainer. If pszContainer is NULL, then a key container with the default name will be created.
Note that when key containers are created, most CSPs will not automatically create any public/private key pairs. These keys must be created as a separate step with the CryptGenKey function.
Important
This flag should only be set by administrative applications. Normal applications should not create key containers.
CRYPT_DELETEKEYSET
If this flag is set, then the key container specified by pszContainer is deleted. If pszContainer is NULL, then the key container with the default name is deleted. All key pairs in the key container are also destroyed.
When the CRYPT_DELETEKEYSET flag is set, the value returned in phProv is undefined and, thus, the CryptReleaseContext function need not be called afterwards.
Important
This flag should only be set by administrative applications. Normal applications should not destroy key containers.
Return Value
If the function succeeds, the return value is TRUE. If it does not succeed, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function.
Error Description ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. ERROR_NOT_ENOUGH_MEMORY The operating system ran out of memory during the operation. NTE_BAD_FLAGS The dwFlags parameter has an illegal value. NTE_BAD_KEYSET The Registry entry for the key container could not be opened and may not exist. NTE_BAD_KEYSET_PARAM The pszContainer or pszProvider parameter is set to an illegal value. NTE_BAD_PROV_TYPE The value of the dwProvType parameter is out of range. All provider types must be from 1 to 999, inclusive. NTE_BAD_SIGNATURE The provider DLL signature did not verify correctly. Either the DLL or the digital signature has been tampered with. NTE_EXISTS The dwFlags parameter is CRYPT_NEWKEYSET, but the key container already exists. NTE_KEYSET_ENTRY_BAD The Registry entry for the pszContainer key container was found (in the HKEY_CURRENT_USER window), but is corrupt. See Chapter 8 for details about CryptoAPI's Registry usage. NTE_KEYSET_NOT_DEF No Registry entry exists in the HKEY_CURRENT_USER window for the key container specified by pszContainer. NTE_NO_MEMORY The CSP ran out of memory during the operation. NTE_PROV_DLL_NOT_FOUND The provider DLL file does not exist or is not on the current path. NTE_PROV_TYPE_ENTRY_BAD The Registry entry for the provider type specified by dwProvType is corrupt. This error may relate to either the user default CSP list or the machine default CSP list. See Chapter 8 for details about CryptoAPI's Registry usage. NTE_PROV_TYPE_NO_MATCH The provider type specified by dwProvType does not match the provider type found in the Registry. Note that this error can only occur when pszProvider specifies an actual CSP name. NTE_PROV_TYPE_NOT_DEF No Registry entry exists for the provider type specified by dwProvType. NTE_PROVIDER_DLL_FAIL The provider DLL file could not be loaded, and may not exist. If it exists, then the file is not a valid DLL. NTE_SIGNATURE_FILE_BAD An error occurred while loading the DLL file image, prior to verifying its signature.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; BYTE pbData[1000]; DWORD dwDataLen; // Get handle to the default PROV_RSA_FULL provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); return; } // Read the name of the default CSP. dwDataLen = 1000; if(!CryptGetProvParam(hProv, PP_NAME, pbData, &dwDataLen, 0)) { printf("Error %x reading CSP name!\n", GetLastError()); return; } printf("Provider name:%s\n", pbData); // Read the name of the default key container. dwDataLen = 1000; if(!CryptGetProvParam(hProv, PP_CONTAINER, pbData, &dwDataLen, 0)) { printf("Error %x reading key container name!\n", GetLastError()); return; } printf("Key Container name:%s\n", pbData); // Perform cryptographic operations. ... // Release provider handle. if(!CryptReleaseContext(hProv, 0)) { printf("Error %x during CryptReleaseContext!\n", GetLastError()); return; } // **************************************************************** // Get handle to the Microsoft RSA Base Provider and the // "Foo" key container. if(!CryptAcquireContext(&hProv, TEXT("Foo"), MS_DEF_PROV, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); return; } // Perform cryptographic operations. ... // Release provider handle. if(!CryptReleaseContext(hProv, 0)) { printf("Error %x during CryptReleaseContext!\n", GetLastError()); return; } // **************************************************************** // Get handle to the default provider. Create a new key container // named "Bar". Note that this key container will be empty until keys // are explicitly created with the CryptGenKey function. lstrcpy(szProv, ); lstrcpy(szContainer, ); if(!CryptAcquireContext(&hProv, TEXT("Bar"), NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); return; } // Perform cryptographic operations. ... // Release provider handle. if(!CryptReleaseContext(hProv, 0)) { printf("Error %x during CryptReleaseContext!\n", GetLastError()); return; }
See Also
CryptGetProvParam, CryptReleaseContext
The CryptGetProvParam function lets applications retrieve parameters that govern the operations of a CSP.
BOOL CRYPTFUNC CryptGetProvParam(
HCRYPTPROV hProv, // in
DWORD dwParam, // in
BYTE *pbData, // out
DWORD *pdwDataLen, // in, out
DWORD dwFlags) // in
Parameters
hProv
A handle to the CSP on which to query parameters.
dwParam
The parameter number. See the "Remarks" section for a list of valid parameters.
pbData
The parameter data buffer. The function will copy the specified parameter data to this buffer. The form of this data will vary, depending on the parameter number.
This parameter can be NULL if all you are doing is determining the number of bytes required for the returned parameter data.
pdwDataLen
The address of the parameter data length. Before calling this function, the caller should set this parameter to the length, in bytes, of the pbData buffer. Upon return, this address will contain the number of bytes of parameter data copied to the buffer.
If the buffer specified by pbData is not large enough to hold the data, the function returns the ERROR_MORE_DATA error code through the GetLastError function, and stores the required buffer size in bytes into the variable pointed to by pdwDataLen.
If pbData is NULL, then no error is returned and the function stores the size of the data in bytes in the variable pointed to by pdwDataLen.
Note
When one of the enumeration parameters (PP_ENUMALGS or PP_ENUMCONTAINERS) is being read, the pdwDataLen parameter works somewhat differently. If pbData is NULL or the value pointed to pdwDataLen is too small, the value returned in this parameter is the size of the largest item in the enumeration list instead of the size of the item currently being read.
When one of the enumeration parameters is read and the pbData parameter is NULL, the CRYPT_FIRST flag must be specified in order for the size information to be correctly retrieved.
dwFlags
The flag values. This parameter is normally set to zero.
When one of the enumeration parameters (PP_ENUMALGS or PP_ENUMCONTAINERS) is being read, then the CRYPT_FIRST flag can be specified. When this flag is set, the first item in the enumeration list is returned. If this flag is not set, then the next item in the list is returned.
Remarks
Parameter Numbers
The dwParam parameter can be set to one of the following values:
PP_CONTAINER
The key container name. When this parameter is specified, the function fills the pbData buffer with the name of the current key container. This name is in the form of a zero-terminated CHAR string.
This string is exactly the same as the one passed in the pszContainer parameter of the CryptAcquireContext function in order to specify that the current key container be used. This parameter is often read in order to determine the name of the default key container.
PP_ENUMALGS
The algorithm information. When this parameter is specified, the function fills the pbData buffer with information about one of the algorithms supported by the CSP. The PP_ENUMALGS parameter must be read repeatedly to enumerate all the supported algorithms. The algorithms are not enumerated in any particular order.
The first time that the PP_ENUMALGS parameter is read, the CRYPT_FIRST flag should be specified. This will ensure that information about the "first" algorithm in the enumeration list is returned. The PP_ENUMALGS parameter can then be repeatedly read in order to retrieve the information about the rest of the algorithms. When the CryptGetProvParam function fails with the ERROR_NO_MORE_ITEMS, then the end of the enumeration list has been reached. A code sample illustrating this is located in the "Example" section.
The following code fragment indicates the format of the data that the function returns in the pbData buffer.
ALG_ID aiAlgid; DWORD dwBits; DWORD dwNameLen; CHAR szName[dwNameLen];
The following table defines each of these fields.
Field Description aiAlgid The algorithm identifier. This is the value that is passed to the CryptGenKey, CryptDeriveKey, or CryptCreateHash functions in order to specify that a particular algorithm be used. dwBits The number of bits in the keys used by the algorithm. If this is a hash algorithm, this value indicates the number of bits in the hash values generated by this algorithm. dwNameLen The number of characters in the algorithm name, including the terminating zero. szName The zero-terminated name of the algorithm.
PP_ENUMCONTAINERS
The key container names. When this parameter is specified, the function fills the pbData buffer with the name of one of the key containers maintained by the CSP. This name is in the form of a zero-terminated CHAR string. The PP_ENUMCONTAINERS parameter must be read repeatedly to enumerate all the container names.
These container names are enumerated in the same way as are the CSP's supported algorithms. Refer to the PP_ENUMALGS for more information.
PP_IMPTYPE
The CSP implementation type. The pbData buffer will contain a DWORD value that indicates how the CSP is implemented. Possible values are:
CRYPT_IMPL_HARDWARE
CRYPT_IMPL_SOFTWARE
CRYPT_IMPL_MIXED
CRYPT_IMPL_UNKNOWN (the CSP isn't telling)
PP_NAME
The CSP name. When this parameter is specified, the function fills the pbData buffer with the name of the CSP. This name is in the form of a zero-terminated CHAR string.
This string is identical to the one passed in the pszProvider parameter of the CryptAcquireContext function in order to specify that the current CSP be used. For example, the Microsoft RSA Base Provider will return "Microsoft Base Cryptographic Provider v1.0" when this parameter is read.
PP_VERSION
The CSP version number. The pbData buffer will contain a DWORD value that indicates the version number of the CSP. The least significant byte contains the minor version number and the next most significant byte the major version number. For example, version 1.0 would be represented here as 0x00000100.
Algorithm Identifiers
When enumerating algorithms, your application may need to determine the class of a particular algorithm. For example, you may want to display a list of encryption algorithms to the user and disregard the rest. This can be done with the GET_ALG_CLASS(x) macro. This macro takes an algorithm identifier as an argument and returns a code indicating the general class of algorithm that the identifier belongs to. Possible return values include:
ALG_CLASS_DATA_ENCRYPT
ALG_CLASS_HASH
ALG_CLASS_KEY_EXCHANGE
ALG_CLASS_SIGNATURE
The following table lists the algorithms supported by the Microsoft RSA Base Provider along with the class of each algorithm.
Name Identifier Class "MD2" CALG_MD2 ALG_CLASS_HASH "MD5" CALG_MD5 ALG_CLASS_HASH "SHA" CALG_SHA ALG_CLASS_HASH "MAC" CALG_MAC ALG_CLASS_HASH "RSA_SIGN" CALG_RSA_SIGN ALG_CLASS_SIGNATURE "RSA_KEYX" CALG_RSA_KEYX ALG_CLASS_KEY_EXCHANGE "RC2" CALG_RC2 ALG_CLASS_DATA_ENCRYPT "RC4" CALG_RC4 ALG_CLASS_DATA_ENCRYPT
If your application does not recognize an algorithm identifier, it is not recommended that it use the algorithm. Making use of an unknown cryptographic algorithm can sometimes produce unpredictable results.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. ERROR_NO_MORE_ITEMS The end of the enumeration list has been reached. No valid data has been placed in the pbData buffer. This error is only returned when dwParam equals PP_ENUMALGS or PP_ENUMCONTAINERS. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_TYPE The dwParam parameter specifies an unknown parameter number. NTE_BAD_UID The CSP context specified by hProv is invalid.
Example
This example shows how the list of algorithms supported by a particular CSP is accessed by an application.
HCRYPTPROV hProv; // Handle to CSP DWORD dwAlgCount; BYTE *ptr = NULL; DWORD i; ALG_ID aiAlgid; DWORD dwBits; DWORD dwNameLen; CHAR szName[100]; // Often allocated dynamically. BYTE pbData[1000]; // Often allocated dynamically. DWORD dwDataLen; DWORD dwFlags; CHAR *pszAlgType = NULL; // Enumerate the supported algorithms. for(i=0 ; ; i++) { // Set the CRYPT_FIRST flag the first time through the loop. if(i == 0) { dwFlags = CRYPT_FIRST; } else { dwFlags = 0; } // Retrieve information about an algorithm. dwDataLen = 1000; if(!CryptGetProvParam(hProv, PP_ENUMALGS, pbData, &dwDataLen, 0)) { if(GetLastError() == ERROR_NO_MORE_ITEMS) { // Exit the loop. break; } else { printf("Error %x reading algorithm!\n", GetLastError()); return; } } // Extract algorithm information from 'pbData' buffer. ptr = pbData; aiAlgid = *(ALG_ID *)ptr; ptr += sizeof(ALG_ID); dwBits = *(DWORD *)ptr; ptr += sizeof(DWORD); dwNameLen = *(DWORD *)ptr; ptr += sizeof(DWORD); strncpy(szName, ptr,dwNameLen); // Determine algorithm type. switch(GET_ALG_CLASS(aiAlgid)) { case ALG_CLASS_DATA_ENCRYPT: pszAlgType = "Encrypt "; break; case ALG_CLASS_HASH: pszAlgType = "Hash "; break; case ALG_CLASS_KEY_EXCHANGE: pszAlgType = "Exchange "; break; case ALG_CLASS_SIGNATURE: pszAlgType = "Signature"; break; default: pszAlgType = "Unknown "; } // Print information about algorithm. printf("Algid:%8.8xh, Bits:%-4d, Type:%s, NameLen:%-2d, Name:%s\n", aiAlgid, dwBits, pszAlgType, dwNameLen, szName ); }
See Also
CryptAcquireContext, CryptSetProvParam
The CryptReleaseContext function is used to release a handle to a CSP and a key container.
This should be performed when the application is finished using the CSP. Once this function is called, the CSP handle specified by the hProv parameter will no longer be valid. Neither the key container nor any key pairs are destroyed by this function.
BOOL CRYPTFUNC CryptReleaseContext(
HCRYPTPROV hProv, // in
DWORD dwFlags) // in
Parameters
hProv
A handle to the application's CSP. This is the handle the application obtained using the CryptAcquireContext function.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
Remarks
Once this function has been called, the "session" is over, and all existing session keys and hash objects that were created using the hProv handle become invalid. In practice, all of these objects should be destroyed (with the CryptDestroyKey and CryptDestroyHash functions) before the CryptReleaseContext function is called.
Return Value
If the function succeeds, the return value is TRUE. If it fails, then the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_BUSY The CSP context specified by hProv is currently being used by another process. ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_UID The hProv parameter does not contain a valid context handle.
Example
See the "Example" section in the CryptAcquireContext function.
See Also
CryptAcquireContext
The CryptSetProvider function is used to specify the current user default CSP.
After this function has been called, any calls this user subsequently makes to CryptAcquireContext specifying the dwProvType provider type but not a provider name, will result in the pszProvName provider being used.
Note
Typical applications will not use this function. It is intended for sole use by administrative applications.
BOOL CRYPTFUNC CryptSetProvider(
LPCTSTR pszProvName, // in
DWORD dwProvType) // in
Parameters
pszProvName
The name of the new default CSP. This CSP should have already been installed on the computer.
dwProvType
The provider type of the CSP specified by the pszProvName parameter.
Remarks
Well-behaved applications do not usually specify a CSP name when calling the CryptAcquireContext function. This gives the users a certain amount of freedom in that they can select a CSP that has an appropriate level of security.
This means that a call to CryptSetProvider will often determine the CSP of a given type used by all applications that run from that point on. With this being the case, the CryptSetProvider function should never be called without the user's consent.
Return Value
If the function succeeds, the return value is TRUE. If the function fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. ERROR_NOT_ENOUGH_MEMORY The operating system ran out of memory during the operation. Error codes returned from See RegCreateKeyEx. the RegCreateKeyEx function. Error codes returned from See RegSetValueEx. the RegSetValueEx function.
Example
HCRYPTPROV hProv = 0; // Specify the default PROV_RSA_SIG provider. Note that this assumes // that a CSP with a type of PROV_RSA_SIG and named "Joe's Provider" // has already been installed. if(!CryptSetProvider(TEXT("Joe's Provider"), PROV_RSA_SIG)) { printf("Error %x during CryptSetProvider!\n", GetLastError()); return; } // Get handle to the provider that we just made default. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_SIG, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); return; } ... // Release provider handle. if(!CryptReleaseContext(hProv, 0)) { printf("Error %x during CryptReleaseContext!\n", GetLastError()); return; }
See Also
CryptAcquireContext
The CryptSetHashParam function lets applications customize the operations of a CSP.
This function is not currently useful, since no settable CSP parameters have yet been defined.
BOOL CRYPTFUNC CryptSetProvParam(
HCRYPTPROV hProv, // in
DWORD dwParam, // in
BYTE *pbData, // in
DWORD dwFlags) // in
Parameters
hProv
A handle to the CSP on which to set parameters.
dwParam
The parameter number to set.
pbData
The parameter data buffer. Place the parameter data in this buffer before calling CryptSetKeyParam. The form of this data will vary, depending on the parameter number.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_BUSY The CSP context is currently being used by another process. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_FLAGS The dwFlags parameter is nonzero or the pbData buffer contains an invalid value. NTE_BAD_TYPE The dwParam parameter specifies an unknown parameter. NTE_BAD_UID The CSP context that was specified when the hKey key was created cannot be found. NTE_FAIL The function failed in some unexpected way.
See Also
CryptAcquireContext, CryptGetProvParam
CHAPTER 10
Key Generation and Exchange Functions
The functions described in this section are used by applications to create, configure, and destroy cryptographic keys, and to exchange them with other users. The following table briefly describes each function:
Function Description CryptDeriveKey Create a key derived from a password. CryptDestroyKey Destroy a key. CryptExportKey Transfer a key from the CSP into a key blob in the application's memory space. CryptGenKey Create a random key. CryptGenRandom Generate random data. CryptGetKeyParam Retrieve a key's parameters. CryptGetUserKey Get a handle to the key exchange or signature key. CryptImportKey Transfer a key from a key blob to a CSP. CryptSetKeyParam Specify a key's parameters.
The CryptDeriveKey function generates cryptographic keys derived from base data. This function guarantees that all keys generated from the same base data will be identical, provided the same CSP and algorithms are used. The base data can be a password or any other user data.
This function is the same as CryptGenKey, except that the generated session keys are derived from base data instead of being random. Another difference is that the CryptDeriveKey function cannot be used to generate public/private key pairs.
A handle to the session key is returned in phKey. This handle can then be used as needed with any of the other CryptoAPI functions that require key handles.
BOOL CRYPTFUNC CryptDeriveKey(
HCRYPTPROV hProv, // in
ALG_ID Algid, // in
HCRYPTHASH hBaseData, // in
DWORD dwFlags, // in
HCRYPTKEY *phKey) // in, out
Parameters
hProv
A handle to the application's CSP. An application obtains this handle using the CryptAcquireContext function.
Algid
The identifier for the algorithm for which the key is to be generated.
The valid values for this parameter will vary, depending on the CSP that is used. See the "Remarks" section for a list of possible algorithm identifiers.
hBaseData
A handle to a hash object that has been fed exactly the base data.
To obtain this handle, an application must first create a hash object with CryptCreateHash and then add the base data to the hash object with CryptHashData. This process is described in detail in Chapter 7.
dwFlags
The flags specifying the type of key generated. This parameter can be zero, or you can specify one or more of the following flags, using the binary OR operator to combine them.
CRYPT_EXPORTABLE
If this flag is set, then the session key can be transferred out of the CSP into a key blob through the CryptExportKey function. Because keys generally must be exportable, this flag should usually be set.
If this flag is not set, then the session key will not be exportable. This means the key will only be available within the current session and only the application that created it will be able to use it.
This flag does not apply to public/private key pairs.
CRYPT_CREATE_SALT
Typically, when a session key is made from a hash value, there are a number of leftover bits. For example, if the hash value is 128 bits and the session key is 40 bits, there will be 88 bits leftover.
If this flag is set, then the key will be assigned a salt value based on the unused hash value bits. You can retrieve this salt value using the CryptGetKeyParam function with the dwParam parameter set to KP_SALT.
If this flag is not set, then the key will be given a salt value of zero.
When keys with nonzero salt values are exported (using CryptExportKey), the salt value must also be obtained and kept with the key blob.
CRYPT_USER_PROTECTED
If this flag is set, then the user will be notified through a dialog box or another method when certain actions are attempted using this key. The precise behavior is specified by the CSP being used.
The Microsoft RSA Base Provider ignores this flag.
CRYPT_UPDATE_KEY
Some CSPs use session keys that are derived from multiple hash values. When this is the case, CryptDeriveKey must be called multiple times.
If this flag is set, a new session key is not generated. Instead, the key specified by phKey is modified. The precise behavior of this flag is dependent on the type of key being generated and on the particular CSP being used.
The Microsoft RSA Base Provider ignores this flag.
phKey
The address to which the function copies the handle of the newly generated key.
Remarks
To generate a key for a symmetric encryption algorithm, use the Algid parameter to specify the algorithm. The algorithms available will most likely be different for each CSP. If you are using the Microsoft RSA Base Provider, use one of the following values to specify the algorithm:
CALG_RC2 - RC2 block cipher
CALG_RC4 - RC4 stream cipher
When keys are generated for symmetric block ciphers, the key by default will be set up in cipher block chaining (CBC) mode with an initialization vector of zero. This cipher mode provides a good default method for bulk encrypting data. To change these parameters, use the CryptSetKeyParam function.
Once the CryptDeriveKey function has been called, no more data can be added to the hash object. The CryptDestroyHash function should be called at this point to destroy the hash object.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The Algid parameter specifies an algorithm that this CSP does not support. NTE_BAD_FLAGS The dwFlags parameter contains an invalid value. NTE_BAD_HASH The hBaseData parameter does not contain a valid handle to a hash object. NTE_BAD_UID The hProv parameter does not contain a valid context handle. NTE_FAIL The function failed in some unexpected way.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; HCRYPTHASH hHash = 0; CHAR szPassword[ ] = "apple-camshaft"; DWORD dwLength; // Get handle to user default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Create hash object. if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { printf("Error %x during CryptCreateHash!\n", GetLastError()); goto done; } // Hash password string. dwLength = strlen(szPassword); if(!CryptHashData(hHash, (BYTE *)szPassword, dwLength, 0)) { printf("Error %x during CryptHashData!\n", GetLastError()); goto done; } // Create block cipher session key based on hash of the password. if(!CryptDeriveKey(hProv, CALG_RC2, hHash, CRYPT_EXPORTABLE, &hKey)) { printf("Error %x during CryptDeriveKey!\n", GetLastError()); goto done; } // Use 'hKey' to do something. ... done: // Destroy hash object. if(hHash != 0) CryptDestroyHash(hHash); // Destroy session key. if(hKey != 0) CryptDestroyKey(hKey); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0);
See Also
CryptCreateHash, CryptDestroyKey, CryptGenKey, CryptHashData
The CryptDestroyKey function releases the handle referenced by the hKey parameter. Once a key handle has been released, it becomes invalid and cannot be used again.
If the handle refers to a session key, or to a public key that has been imported into the CSP through CryptImportKey, this function destroys the key and frees the memory that the key occupied. Many CSPs will scrub the memory where the key was held before freeing it.
On the other hand, if the handle refers to a public/private key pair (obtained from CryptGetUserKey), the underlying key pair is not destroyed by this function. Only the handle is destroyed.
BOOL CRYPTFUNC CryptDestroyKey(
HCRYPTKEY hKey) // in
Parameters
hKey
A handle to the key to be destroyed.
Remarks
Keys take up memory in both the operating system's memory space and the CSP's memory space. Some CSPs will be implemented in hardware with very limited memory resources. For this reason, it is important that applications destroy all keys with the CryptDestroyKey function when they are finished with them.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_KEY The hKey parameter does not contain a valid handle to a key. NTE_BAD_UID The CSP context that was specified when the key was created cannot be found.
Example
See the "Example" section in the CryptGenKey function.
See Also
CryptDeriveKey, CryptGenKey, CryptGetUserKey
The CryptExportKey function is used to export cryptographic keys out of a cryptographic service provider in a secure manner.
A handle to the key to be exported is passed into the function and the function returns a key blob to the caller. This key blob can be sent over a nonsecure transport or stored in a nonsecure storage location. The key blob is useless until the intended recipient uses the CryptImportKey function on it, which will then import the key into the recipient's CSP.
BOOL CRYPTFUNC CryptExportKey(
HCRYPTKEY hKey, // in
HCRYPTKEY hExpKey, // in
DWORD dwBlobType, // in
DWORD dwFlags, // in
BYTE *pbData, // out
DWORD *pdwDataLen) // in, out
Parameters
hKey
A handle to the key to be exported.
hExpKey
A handle to a cryptographic key belonging to the destination user. The key data within the key blob created is encrypted using this key. This ensures that only the destination user will be able to make use of the key blob.
Most often, this will be the key exchange public key of the destination user. However, certain protocols require that a session key belonging to the destination user be used for this purpose.
If the key blob type specified by dwBlobType is PUBLICKEYBLOB, then this parameter is unused and should be set to zero.
dwBlobType
The type of key blob to be exported. This must currently be one of the following constants. These constants are discussed in Chapter 5.
SIMPLEBLOB
PUBLICKEYBLOB
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
pbData
The buffer that the function places the key blob in. The required size for this buffer can be determined by calling CryptExportKey with NULL for this parameter.
As a rule, SIMPLEBLOBs will be 256 bytes or less, and PUBLICKEYBLOBs will be 1024 bytes or less.
pdwDataLen
The address of the key blob data length. Before calling this function, the caller should set this parameter to the length, in bytes, of the pbData buffer. Upon return, this address will contain the number of bytes taken up by the key blob.
If the buffer specified by pbData is not large enough to hold the data, the function returns the ERROR_MORE_DATA error code (through GetLastError) and stores the required buffer size, in bytes, into the variable pointed to by pdwDataLen.
If pbData is NULL, then no error is returned and the function stores the size of the data, in bytes, in the variable pointed to by pdwDataLen.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_KEY One or both of the keys specified by hKey and hExpKey are invalid. NTE_BAD_KEY_STATE You do not have permission to export the key. That is, when the hKey key was created, the CRYPT_EXPORTABLE flag was not specified. NTE_BAD_PUBLIC_KEY The key blob type specified by dwBlobType is PUBLICKEYBLOB, but hExpKey does not contain a public key handle. NTE_BAD_TYPE The dwBlobType parameter specifies an unknown blob type. NTE_BAD_UID The CSP context that was specified when the hKey key was created cannot be found. NTE_NO_KEY A session key is being exported and the hExpKey parameter does not specify a public key.
Example
#include <wincrypt.h> HCRYPTPROV hProv; // Handle to CSP HCRYPTKEY hKey; // Handle to session key HCRYPTKEY hXchgKey; // Handle to receiver's exchange public key BYTE *pbKeyBlob = NULL; DWORD dwBlobLen; ... // Determine size of key blob and allocate memory. if(!CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, NULL, &dwBlobLen)) { printf("Error %x computing blob length!\n", GetLastError()); ... } if((pbKeyBlob = malloc(dwBlobLen)) == NULL) { printf("Out of memory!\n"); ... } // Export key into a simple key blob. if(!CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, pbKeyBlob, &dwBlobLen)) { printf("Error %x during CryptExportKey!\n", GetLastError()); ... }
See Also
CryptImportKey
The CryptGenKey function generates random cryptographic keys for use with the CSP module. A handle to the key is returned in phKey. This handle can then be used as needed with any of the other CryptoAPI functions requiring key handles.
The calling application is required to specify the algorithm when calling this function. Because this algorithm type is kept bundled with the key, the application does not need to specify the algorithm later when the actual cryptographic operations are performed.
BOOL CRYPTFUNC CryptGenKey(
HCRYPTPROV hProv, // in
ALG_ID Algid, // in
DWORD dwFlags, // in
HCRYPTKEY *phKey) // out
Parameters
hProv
A handle to the application's CSP. An application obtains this handle using the CryptAcquireContext function.
Algid
The identifier for the algorithm for which the key is to be generated.
The valid values for this parameter will vary, depending on the CSP that is used. See the "Remarks" section for a list of possible algorithm identifiers.
dwFlags
The flags specifying the type of key generated. This parameter can be zero, or you can specify one or more of the following flags, using the binary OR operator to combine them.
CRYPT_EXPORTABLE
If this flag is set, then the session key can be transferred out of the CSP into a key blob using the CryptExportKey function. Because keys generally must be exportable, this flag should usually be set.
If this flag is not set, then the session key will not be exportable. This means that the key will only be available within the current session and only the application that created it will be able to use it.
This flag does not apply to public/private key pairs.
CRYPT_CREATE_SALT
If this flag is set, then the key will be assigned a random salt value automatically. You can retrieve this salt value using the CryptGetKeyParam function with the dwParam parameter set to KP_SALT.
If this flag is not set, then the key will be given a salt value of zero.
When keys with non-zero salt values are exported (through CryptExportKey), then the salt value must also be obtained and kept with the key blob.
CRYPT_USER_PROTECTED
If this flag is set, then the user will be notified through a dialog box or another method when certain actions are attempted using this key. The precise behavior is specified by the CSP being used.
The Microsoft RSA Base Provider ignores this flag.
phKey
The address that the function copies the handle of the newly generated key to.
Remarks
To generate a key to be used with a symmetric encryption algorithm (that is, a session key), use the Algid parameter to specify the algorithm. The algorithms available will most likely be different for each CSP. If you are using the Microsoft RSA Base Provider, one of the following values can be used to specify the algorithm:
CALG_RC2 - RC2 block cipher
CALG_RC4 - RC4 stream cipher
When keys are generated for symmetric block ciphers, the key by default will be set up in cipher block chaining (CBC) mode with an initialization vector of zero. This cipher mode provides a good default method for bulk encrypting data. To change these parameters, use the CryptSetKeyParam function.
In addition to generating keys for symmetric algorithms, the CryptGenKey function can also generate keys for public-key algorithms. The use of public-key algorithms is restricted to key exchange and digital signatures. Each CryptoAPI client generally possesses one key pair for each of these operations. To generate one of these key pairs, set the Algid parameter to one of the following values:
AT_KEYEXCHANGE - Key exchange
AT_SIGNATURE - Digital signature
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The Algid parameter specifies an algorithm that this CSP does not support. NTE_BAD_FLAGS The dwFlags parameter contains an invalid value. NTE_BAD_UID The hProv parameter does not contain a valid context handle. NTE_FAIL The function failed in some unexpected way.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; // Get handle to user default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Create block cipher session key. if(!CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKey)) { printf("Error %x during CryptGenKey!\n", GetLastError()); goto done; } // Use 'hKey' to do something. ... done: // Destroy session key. if(hKey != 0) CryptDestroyKey(hKey); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0);
See Also
CryptDestroyKey, CryptExportKey, CryptImportKey
The CryptGenRandom function fills a buffer with random bytes.
BOOL CRYPTFUNC CryptGenRandom(
HCRYPTPROV hProv, // in
DWORD dwLen, // in
BYTE *pbBuffer) // in, out
Parameters
hProv
A handle to the application's CSP. An application obtains this handle using the CryptAcquireContext function.
dwLen
The number of bytes of random data to be generated.
pbBuffer
The buffer the function is to copy the random data to. This buffer must be at least dwLen bytes in length.
Optionally, the application can fill this buffer with data to use as an auxiliary random seed. This is explained further in the "Remarks" section.
Remarks
The data produced by this function is "cryptographically random." It is far more random than the data generated by the typical random number generator such as the one shipped with your "C" compiler.
This function is often used to generate random initialization vectors and salt values.
Seeding the Random Number Generator
All software random number generators work in fundamentally the same way. They start with one truly random number, known as the "seed," and then use an algorithm to generate a pseudo-random sequence of bits based on it. The most difficult part of this process is to get a seed that is truly random. This is usually based on user input latency, or the jitter from one or more hardware components.
If your application has access to a good random source, then it can fill the pbBuffer buffer with some amount of random data before calling CryptGenRandom. The CSP will then use this data to further randomize its internal seed. Failing to initialize the pbBuffer buffer before calling CryptGenRandom is acceptable.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_UID The hProv parameter does not contain a valid context handle. NTE_FAIL The function failed in some unexpected way.
Example
See the "Example" section in the CryptSetKeyParam function.
See Also
CryptAcquireContext, CryptSetKeyParam
The CryptGetKeyParam function lets applications retrieve data that governs of the operations of a key. Note that the base keying material is not obtainable by this function or any other function.
BOOL CRYPTFUNC CryptGetKeyParam(
HCRYPTKEY hKey, // in
DWORD dwParam, // in
BYTE *pbData, // out
DWORD *pdwDataLen, // in, out
DWORD dwFlags) // in
Parameters
hKey
A handle to the key on which to query parameters.
dwParam
The parameter number. See the "Remarks" section for a list of valid parameters.
pbData
The parameter data buffer. The function will copy the specified parameter data to this buffer. The form of this data will vary, depending on the parameter number.
This parameter can be NULL if all you are doing is determining the number of bytes required for the returned parameter data.
pdwDataLen
The address of the parameter data length. Before calling this function, the caller should set this parameter to the length, in bytes, of the pbData buffer. Upon return, this address will contain the number of bytes of parameter data copied to the buffer.
If the buffer specified by pbData is not large enough to hold the data, the function returns the ERROR_MORE_DATA error code (through GetLastError) and stores the required buffer size, in bytes, into the variable pointed to by pdwDataLen.
If pbData is NULL, then no error is returned and the function stores the size of the data, in bytes, in the variable pointed to by pdwDataLen.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
Remarks
For all key types, the dwParam value can be set to one of the following key parameter types:
Parameter Description KP_ALGID Key algorithm. The pbData buffer will contain an ALG_ID value indicating that the algorithm was specified when the key was created. KP_BLOCKLEN If a session key is specified by hKey, this parameter returns the block length, in bytes, of the cipher. The pbData buffer will contain a DWORD value indicating the block length. For stream ciphers, this value will always be zero. If a public/private key pair is specified by hKey, this parameter returns the key pair's encryption granularity in bits. For example, the Microsoft RSA Base Provider generates 512-bit RSA key pairs, so a value of 512 is returned for these keys. If the public-key algorithm does not support encryption, the value returned by this parameter is undefined. KP_SALT The salt value. The pbData buffer will contain a BYTE array indicating the current salt value. The size of the salt value will vary depending on the CSP and algorithm being used. Before setting this parameter, it should be read using CryptGetKeyParam in order to determine the size. Salt values do not apply to public/private key pairs. KP_PERMISSIONS Key permissions. The pbData buffer will contain a DWORD value with zero or more permission flags set. Refer to the table at the end of this section for a description of each of these flags.
If a block cipher session key is specified by hKey, the dwParam value can also be set to one of the following parameter types.
Parameter Description KP_IV The initialization vector. The pbData buffer will contain a BYTE array indicating the current initialization vector. This array contains <block length>/8 elements. For example, if the block length is 64 bits, the initialization vector will consist of 8 bytes. KP_PADDING The padding mode. The pbData buffer will contain a DWORD value indicating the padding method used by the cipher. Following are the padding modes currently defined: PKCS5_PADDING - PKCS 5 (sec 6.2) padding method. KP_MODE The cipher mode. The pbData buffer will contain a DWORD value indicating the mode of the cipher. Refer to the following table for a list of valid cipher modes. KP_MODE_BITS The number of bits to feed back. The pbData buffer will contain a DWORD value indicating the number of bits that are processed per cycle when the OFB or CFB cipher modes are used.
The following table lists the possible values for the KP_MODE parameter. These cipher modes are discussed in Chapter 6.
Cipher Mode Description CRYPT_MODE_ECB Electronic codebook. CRYPT_MODE_CBC Cipher block chaining. CRYPT_MODE_OFB Output feedback mode. CRYPT_MODE_CFB Cipher feedback mode.
The following table lists the flags in the bit field that are obtained when the KP_PERMISSIONS parameter is read. These permission flags are ignored by the Microsoft RSA Base Provider. However, custom CSPs can use these flags to restrict operations on keys.
Permission Flag Description CRYPT_ENCRYPT Allow encryption. CRYPT_DECRYPT Allow decryption. CRYPT_EXPORT Allow key to be exported. CRYPT_READ Allow parameters to be read. CRYPT_WRITE Allow parameters to be set. CRYPT_MAC Allow MACs to be used with key.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_KEY or NTE_NO_KEY The key specified by the hKey parameter is invalid. NTE_BAD_TYPE The dwParam parameter specifies an unknown parameter number. NTE_BAD_UID The CSP context that was specified when the key was created cannot be found.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; DWORD dwMode; BYTE pbData[16]; DWORD dwCount; DWORD i; // Get handle to user default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Create random block cipher session key. if(!CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKey)) { printf("Error %x during CryptGenKey!\n", GetLastError()); goto done; } // Read the cipher mode. dwCount = sizeof(DWORD); if(!CryptGetKeyParam(hKey, KP_MODE, &dwMode, &dwCount, 0)) { printf("Error %x during CryptGetKeyParam!\n", GetLastError()); goto done; } assert(dwCount==sizeof(BYTE)); // Print out cipher mode. printf("Default cipher mode:%d\n", dwMode); // Read initialization vector. dwCount = 16; if(!CryptGetKeyParam(hKey, KP_IV, pbData, &dwCount, 0)) { printf("Error %x during CryptGetKeyParam!\n", GetLastError()); goto done; } // Print out initialization vector. printf("Default IV:"); for(i=0;i<dwCount;i++) printf("%2.2x ",pbData[i]); printf("\n"); done: // Destroy session key. if(hKey != 0) CryptDestroyKey(hKey); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0);
See Also
CryptSetKeyParam
The CryptGetUserKey function retrieves a handle to a permanent user key pair, such as the user's signature key pair.
BOOL CRYPTFUNC CryptGetUserKey(
HCRYPTPROV hProv, // in
DWORD dwKeySpec, // in
HCRYPTKEY *phUserKey) // out
Parameters
hProv
A handle to the application's CSP. An application obtains this handle using the CryptAcquireContext function.
dwKeySpec
The specification of the key to retrieve. The following keys are retrievable from almost all providers:
AT_KEYEXCHANGE - Exchange public key
AT_SIGNATURE - Signature public key
Additionally, some providers allow access to other user specific keys through this function. See the documentation on the specific provider for details.
phUserKey
The address that the function copies the handle of the retrieved key to.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_KEY The dwKeySpec parameter contains an invalid value. NTE_BAD_UID The hProv parameter does not contain a valid context handle. NTE_NO_KEY The key requested by the dwKeySpec parameter does not exist.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; HCRYPTKEY hSignKey = 0; HCRYPTKEY hXchgKey = 0; // Get handle to user default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Get handle to signature key. if(!CryptGetUserKey(hProv, AT_SIGNATURE, &hSignKey)) { printf("Error %x during CryptGetUserKey!\n", GetLastError()); goto done; } // Get handle to key exchange key. if(!CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hXchgKey)) { printf("Error %x during CryptGetUserKey!\n", GetLastError()); goto done; } // Do something with 'hSignKey' and 'hXchgKey'. ... done: // Destroy signature key handle. if(hSignKey != 0) CryptDestroyKey(hSignKey); // Destroy key exchange key handle. if(hXchgKey != 0) CryptDestroyKey(hXchgKey); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0);
See Also
CryptAcquireContext, CryptDestroyKey, CryptGenKey
The CryptImportKey function is used to transfer a cryptographic key from a key blob to the CSP.
BOOL CRYPTFUNC CryptImportKey(
HCRYPTPROV hProv, // in
BYTE *pbData, // in
DWORD dwDataLen, // in
HCRYPTKEY hImpKey, // in
DWORD dwFlags, // in
HCRYPTKEY *phKey) // out
Parameters
hProv
A handle to the application's CSP. An application obtains this handle using the CryptAcquireContext function.
pbData
The buffer containing the key blob. This key blob was generated by the CryptExportKey function, either by this same application or by another application running on a distant computer.
This key blob consists of a standard header followed by the encrypted key.
dwDataLen
The length, in bytes, of the key blob.
hImpKey
The meaning of this parameter differs, depending on the CSP type and the type of key blob being imported.
If the key blob is not encrypted (for example, a PUBLICKEYBLOB) or if the key blob is encrypted with the key exchange key pair (for example, a SIMPLEBLOB), then this parameter is not used, and should be zero.
If a signed key blob is being imported, this key is used to validate the signature of the key blob. In this case, this parameter should contain a handle to the key exchange public key of the party that created the key blob.
If the key blob is encrypted with a session key (as is is often done by Fortezza CSPs, for example), then this parameter should contain a handle to this session key.
The Microsoft RSA Base Provider does not use this parameter, and expects it to be zero.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
phKey
The address to which the function copies a handle to the key that was imported.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The simple key blob you are trying to import is not encrypted with the expected key exchange algorithm. NTE_BAD_DATA The algorithm that works with the public key you are trying to import is not supported by this CSP. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_TYPE The key blob type is not supported by this CSP and is possibly invalid. NTE_BAD_UID The hProv parameter does not contain a valid context handle. NTE_BAD_VER The key blob's version number does not match the CSP version. This usually indicates that the CSP needs to be upgraded.
Example
#include <wincrypt.h> FILE *hSourceFile = NULL; HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; BYTE *pbKeyBlob = NULL; DWORD dwBlobLen; // Open file, getting file handle 'hSourceFile'. ... // Get handle to the default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Read key blob length from file and allocate memory. fread(&dwBlobLen, sizeof(DWORD), 1, hSourceFile); pbKeyBlob = malloc(dwBlobLen); // Read key blob from file. fread(pbKeyBlob, 1, dwBlobLen, hSourceFile); // Import key blob into CSP. if(!CryptImportKey(hProv, pbKeyBlob, dwBlobLen, 0, 0, &hKey)) { printf("Error %x during CryptImportKey!\n", GetLastError()); free(pbKeyBlob); goto done; } // Free memory. free(pbKeyBlob); // Use 'hKey' to perform cryptographic operations. ... done: // Destroy session key. if(hKey) CryptDestroyKey(hKey); // Release provider handle. if(hProv) CryptReleaseContext(hProv, 0);
See Also
CryptDestroyKey, CryptExportKey
The CryptSetKeyParam function lets applications customize various aspects of a key's operations.
Generally, this function is used to set session-specific parameters on symmetric keys. Note that the base keying material is not accessible by this function.
The Microsoft RSA Base Provider has no settable parameters on key exchange or signature keys. However, custom providers may define parameters that can be set on these keys.
BOOL CRYPTFUNC CryptSetKeyParam(
HCRYPTKEY hKey, // in
DWORD dwParam, // in
BYTE *pbData, // in
DWORD dwFlags) // in
Parameters
hKey
A handle to the key on which to set parameters.
dwParam
The parameter number. See the "Remarks" section for a list of valid parameters.
pbData
The parameter data buffer. Place the parameter data in this buffer before calling CryptSetKeyParam. The form of this data will vary, depending on the parameter number.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
Remarks
For all session key types, the dwParam value can be set to one of the following key parameter types;
Parameter Description KP_SALT The salt value. The pbData buffer should contain a BYTE array specifying a new salt value. This value is made part of the session key. The size of the salt value will vary depending on the CSP being used so, before setting this parameter, it should be read using CryptGetKeyParam in order to determine its size. When it is suspected that the base data used for derived keys is less than ideal, salt values are often used to make the session keys more unique. This makes dictionary attacks more difficult. When using the Microsoft RSA Base Provider, this parameter defaults to zero. KP_PERMISSIONS The key permissions flags. The pbData buffer should contain a DWORD value specifying zero or more permission flags. Refer to the CryptGetKeyParam function for a description of these flags. When using the Microsoft RSA Base Provider, this parameter defaults to 0xFFFFFFFF.
If a block cipher session key is specified by hKey, the dwParam value can also be set to one of the following parameter types.
Parameter Description KP_IV The initialization vector. The pbData buffer should contain a BYTE array specifying the initialization vector. This array should contain <block length>/8 elements. For example, if the block length is 64 bits, the initialization vector will consist of 8 bytes. When using the Microsoft RSA Base Provider, this parameter defaults to zero. KP_PADDING The padding mode. The pbData buffer should contain a DWORD value specifying the padding method to be used by the cipher. Following are the padding modes currently defined: PKCS5_PADDING - PKCS 5 (sec 6.2) padding method. When using the Microsoft RSA Base Provider, this parameter defaults to PKCS5_PADDING. KP_MODE The cipher mode. The pbData buffer should contain a DWORD value specifying the cipher mode to be used. Refer to the CryptGetKeyParam function for a list of the defined cipher modes. When using the Microsoft RSA Base Provider, this parameter defaults to CRYPT_MODE_CBC. KP_MODE_BITS The number of bits to feed back. The pbData buffer contains a DWORD value indicating the number of bits that are processed per cycle when the OFB or CFB cipher modes are used. When using the Microsoft RSA Base Provider, this parameter defaults to 8.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_BUSY The CSP context is currently being used by another process. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_FLAGS The dwFlags parameter is nonzero or the pbData buffer contains an invalid value. NTE_BAD_TYPE The dwParam parameter specifies an unknown parameter. NTE_BAD_UID The CSP context that was specified when the hKey key was created cannot be found. NTE_FAIL The function failed in some unexpected way.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; HCRYPTKEY hKey = 0; DWORD dwMode; BYTE pbData[16]; DWORD dwCount; DWORD i; // Get handle to user default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Create random block cipher session key. if(!CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKey)) { printf("Error %x during CryptGenKey!\n", GetLastError()); goto done; } // Set the cipher mode. dwMode = CRYPT_MODE_ECB; if(!CryptSetKeyParam(hKey, KP_MODE, &dwMode, 0)) { printf("Error %x during CryptSetKeyParam!\n", GetLastError()); goto done; } // Generate random initialization vector. if(!CryptGenRandom(hProv, 8, pbData)) { printf("Error %x during CryptGenRandom!\n", GetLastError()); goto done; } // Set initialization vector. if(!CryptSetKeyParam(hKey, KP_IV, pbData, 0)) { printf("Error %x during CryptGetKeyParam!\n", GetLastError()); goto done; } // Do something with 'hKey'. ... done: // Destroy session key. if(hKey != 0) CryptDestroyKey(hKey); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0);
See Also
CryptGenKey, CryptGetKeyParam
CHAPTER 11
The functions in this section support encryption and decryption operations. Before invoking these functions, you must first acquire an encryption key. This is done using CryptGenKey, CryptDeriveKey, or CryptImportKey, which are defined in Chapter 4. The encryption algorithm is specified when the key is created. You can also specify additional encryption parameters using the CryptSetKeyParam function.
Function Description CryptDecrypt Decrypt a section of cipher text using the specified encryption key. CryptEncrypt Encrypt a section of plaintext using the specified encryption key.
The CryptDecrypt function is used to decrypt data that was previously encrypted via the CryptEncrypt function.
BOOL CRYPTFUNC CryptDecrypt(
HCRYPTKEY hKey, // in
HCRYPTHASH hHash, // in
BOOL Final, // in
DWORD dwFlags, // in
BYTE *pbData, // in, out
DWORD *pdwDataLen) // in, out
Parameters
hKey
A handle to the key to use for the decryption. An application obtains this handle by using either the CryptGenKey or CryptImportKey function.
This key specifies the decryption algorithm that is used.
hHash
A handle to a hash object. This parameter is only used if a hash of the data is to be computed. See the "Remarks" section for more information.
If no hash is to be done, this parameter must be zero.
Final
The Boolean value that specifies whether this is the last section in a series being decrypted. This will be TRUE if this is the last or only block. If it is not, then it will be FALSE. See the "Remarks" section for more information.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
pbData
The buffer holding the data to be decrypted. Once that decryption has been performed, the plaintext is placed back in this same buffer.
The number of encrypted bytes in this buffer is specified by pdwDataLen.
pdwDataLen
The address of the data length. Before calling this function, the caller should set this parameter to the number of bytes to be decrypted. Upon return, this address will contain the number of bytes of plaintext generated.
When a block cipher is used, this data length must be a multiple of the block size, unless this is the final section of data to be decrypted and the Final flag is TRUE.
Remarks
If data is to be decrypted and hashed simultaneously, a handle to a hash object can be passed in the hHash parameter. The hash value will be updated with the decrypted plaintext. This option is useful when simultaneously decrypting and verifying a signature.
Prior to calling CryptDecrypt, the application should obtain a handle to the hash object by calling the CryptCreateHash function. Once the decryption is complete, the hash value can be obtained (through CryptGetHashParam) or it can be signed (through CryptSignHash), or it can be used to verify a digital signature (through CryptVerifySignature).
When a large amount of data needs to be decrypted, it can be done in sections. This is done by calling CryptDecrypt repeatedly. The Final parameter should be set to TRUE only on the last invocation of CryptDecrypt, so the decryption engine can properly finish the decryption process. The following extra actions are performed when Final is TRUE:
If the key is a block cipher key, the data will be padded to a multiple of the block size of the cipher. To find the block size of a cipher, use CryptGetKeyParam to get the KP_BLOCKLEN parameter of the key.
If the cipher is operating in a chaining mode, the next CryptDecrypt operation will reset the cipher's feedback register to the KP_IV value of the key.
If the cipher is a stream cipher, the next CryptDecrypt call will reset the cipher to its initial state.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID One of the parameters contains an _PARAMETER invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The hKey session key specifies an algorithm that this CSP does not support. NTE_BAD_DATA The data to be decrypted is invalid. For example, when a block cipher is used and the Final flag FALSE, the value specified by pdwDataLen must be a multiple of the block size. This error can also be returned when the padding is found to be invalid. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_HASH The hHash parameter contains an invalid handle. NTE_BAD_KEY The hKey parameter does not contain a valid handle to a key. NTE_BAD_LEN The size of the output buffer is too small to hold the generated plaintext. NTE_BAD_UID The CSP context that was specified when the key was created cannot be found. NTE_DOUBLE_ENCRYPT The application attempted to decrypt the same data twice. NTE_FAIL The function failed in some unexpected way.
Example
See "Decryption Example" in Chapter 6.
See Also
CryptCreateHash, CryptEncrypt, CryptGenKey, CryptGetHashParam, CryptSignHash, CryptVerifySignature
The CryptEncrypt function is used to encrypt data. The algorithm used to encrypt the data is designated by the key held by the CSP module, which is referenced by the hKey parameter.
BOOL CRYPTFUNC CryptEncrypt(
CRYPTHANDLE hKey, // in
HCRYPTHASH hHash, // in
BOOL Final, // in
DWORD dwFlags, // in
BYTE *pbData, // in, out
DWORD *pdwDataLen, // in, out
DWORD dwBufLen) // in
Parameters
hKey
A handle to the key to use for the encryption. An application obtains this handle by using either the CryptGenKey or the CryptImportKey function.
This key specifies the encryption algorithm that is used.
hHash
A handle to a hash object. This parameter is used only if a hash of the data is to be computed at the same time the encryption is being performed. See the "Remarks" section for more information.
If no hash is to be done, this parameter must be zero.
Final
The Boolean value that specifies whether this is the last section in a series being encrypted. This should be TRUE if this is the last or only block, and FALSE if it is not. See the "Remarks" section for more information.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
pbData
The buffer holding the data to be encrypted. Once the encryption has been performed, the encrypted data is placed back in this same buffer.
The size of this buffer is specified by dwBufLen. The number of bytes of data to be encrypted is specified by pdwDataLen.
This parameter can be NULL if all you are doing is determining the number of bytes required for the returned data.
pdwDataLen
The address of the data length. Before calling this function, the caller should set this parameter to the number of bytes to be encrypted. Upon return, this address will contain the number of bytes of encrypted data.
If the buffer specified by pbData is not large enough to hold the data, the function returns the ERROR_MORE_DATA error code (through GetLastError) and stores the required buffer size, in bytes, into the variable pointed to by pdwDataLen.
If pbData is NULL, then no error is returned, and the function stores the size of the data, in bytes, in the variable pointed to be pdwDataLen. This lets an application determine the correct buffer size unambiguously.
When a block cipher is used, this data length must be a multiple of the block size, unless this is the final section of data to be encrypted and the Final flag is TRUE.
dwBufLen
The number of bytes in the pbData buffer.
Note that, depending on the algorithm used, the encrypted text can be slightly larger than the original plaintext. In this case, the pbData buffer needs to be sized accordingly.
As a rule, if a stream cipher is used the ciphertext will be the same size as the plaintext. If a block cipher is used, the ciphertext will be up to a "block length" larger than the plaintext.
Remarks
If data is to be hashed and encrypted simultaneously, a handle to a hash object can be passed in the hHash parameter. The hash value will be updated with the plaintext passed in. This option is useful when generating signed and encrypted text.
Prior to calling CryptEncrypt, the application should obtain a handle to the hash object by calling the CryptCreateHash function. Once the encryption is complete, the hash value can be obtained through the CryptGetHashParam function or the hash can be signed using the CryptSignHash function.
When a large amount of data needs to be encrypted, it can be done in sections. This is done by calling CryptEncrypt repeatedly. The Final parameter should be set to TRUE only on the last invocation of CryptEncrypt, so the encryption engine can properly finish the encryption process. The following extra actions are performed when Final is TRUE:
If the key is a block cipher key, the data will be padded to a multiple of the block size of the cipher. To find the block size of a cipher, use CryptGetKeyParam to get the KP_BLOCKLEN parameter of the key.
If the cipher is operating in a chaining mode, the next CryptEncrypt operation will reset the cipher's feedback register to the KP_IV value of the key.
If the cipher is a stream cipher, the next CryptEncrypt will reset the cipher to its initial state.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The hKey session key specifies an algorithm that this CSP does not support. NTE_BAD_DATA The data to be encrypted is invalid. For example, when a block cipher is used and the Final flag is FALSE, the value specified by pdwDataLen must be a multiple of the block size. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_HASH The hHash parameter contains an invalid handle. NTE_BAD_KEY The hKey parameter does not contain a valid handle to a key. NTE_BAD_LEN The size of the output buffer is too small to hold the generated ciphertext. NTE_BAD_UID The CSP context that was specified when the key was created cannot be found. NTE_DOUBLE_ENCRYPT The application attempted to encrypt the same data twice. NTE_FAIL The function failed in some unexpected way. NTE_NO_MEMORY The CSP ran out of memory during the operation.
Example
See "Encryption Example" in Chapter 6.
See Also
CryptDecrypt, CryptGenKey
CHAPTER 12
Hashing and Digital Signature Functions
The functions described in this section are used by applications to compute hashes (also known as message digests), and are also used to create and verify digital signatures. The following table briefly describes each function.
Function Description CryptCreateHash Create an "empty" hash object. CryptDestroyHash Destroy a hash object. CryptGetHashParam Retrieve a hash object parameter. CryptHashData Hash a block of data, adding it to the specified hash object. CryptHashSessionKe Hash a session key, adding it to the y specified hash object. CryptSetHashParam Set a hash object parameter. CryptSignHash Sign the specified hash object. CryptVerifySignatu Verify a digital signature, given a re handle to the hash object that was supposedly signed.
The CryptCreateHash function is used to initiate the hashing of a stream of data. It returns to the caller a handle to a CSP hash object. This handle can also be used in subsequent calls to CryptHashData and CryptHashSessionKey in order to hash streams of data and session keys.
BOOL CRYPTFUNC CryptCreateHash(
HCRYPTPROV hProv, // in
ALG_ID Algid, // in
HCRYPTKEY hKey, // in
DWORD dwFlags, // in
HCRYPTHASH *phHash) // out
Parameters
hProv
A handle to the CSP to use. An application obtains this handle using the CryptAcquireContext function.
Algid
An algorithm identifier of the hash algorithm to use.
The valid values for this parameter will vary, depending on the CSP that is used. See the "Remarks" section for the list of default algorithms.
hKey
If the type of hash algorithm is a keyed hash, such as a MAC algorithm, the key for the hash should be passed in this parameter. For nonkeyed algorithms, this parameter should be set to zero.
The key must be to a block cipher, such as RC2, with a cipher mode of CBC.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
phHash
The address to which the function copies a handle to the new hash object.
Remarks
The Microsoft RSA Base Provider defines the following hashing algorithms:
Constant Description CALG_MAC Message Authentication Code CALG_MD2 MD2 CALG_MD5 MD5 CALG_SHA US DSA Secure Hash Algorithm
The computation of the actual hash is done with the CryptHashData and CryptHashSessionKey functions. These require a handle to the hash object. Once all the data has been added to the hash object, exactly one of the following operations can be performed:
The hash value can be retrieved using CryptGetHashParam.
A session key can be derived using CryptDeriveKey.
The hash can be signed using CryptSignHash.
A signature can be verified using CryptVerifySignature.
Once one of the functions from this list has been called, the only hashing function that can be used with the same hash handle is CryptDestroyHash.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. ERROR_NOT_ENOUGH_MEMORY The operating system ran out of memory during the operation. NTE_BAD_ALGID The Algid parameter specifies an algorithm that this CSP does not support. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_KEY A keyed hash algorithm (such as CALG_MAC) is specified by Algid and the hKey parameter is either zero or it specifies an invalid key handle. This error code will also be returned if the key is to a stream cipher, or if the cipher mode is anything other than CBC. NTE_NO_MEMORY The CSP ran out of memory during the operation.
Example
See the "Example" section in the CryptSignHash function.
See Also
CryptDeriveKey, CryptHashData, CryptHashSessionKey, CryptSignHash, CryptVerifySignature
The CryptDestroyHash function destroys the hash object referenced by the hHash parameter. Once a hash object has been destroyed, it can no longer be used and its handle is useless from then on.
All hash objects should be destroyed with the CryptDestroyHash function when the application is finished with them.
BOOL CRYPTFUNC CryptDestroyHash(
HCRYPTHASH hHash) // in
Parameters
hHash
A handle to the hash object to be destroyed.
Remarks
When a hash object is destroyed, the many CSPs will scrub the memory in the CSP where the hash object was held. The CSP memory is then freed.
There should be a one-to-one correspondence between calls to CryptCreateHash and CryptDestroyHash.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_BUSY The hash object specified by hHash is currently being used by another process. ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The hHash handle specifies an algorithm that this CSP does not support. NTE_BAD_HASH The hash object specified by the hHash parameter is invalid. NTE_BAD_UID The CSP context that was specified when the hash object was created cannot be found.
Example
See the "Example" section in the CryptSignHash function.
See Also
CryptCreateHash, CryptHashData, CryptSignHash
The CryptGetHashParam function lets applications retrieve data that governs of the operations of a hash object. The actual hash value can also be retrieved using this function.
BOOL CRYPTFUNC CryptGetHashParam(
HCRYPTHASH hHash, // in
DWORD dwParam, // in
BYTE *pbData, // out
DWORD *pdwDataLen, // in, out
DWORD dwFlags) // in
Parameters
hHash
A handle to the hash object on which to query parameters.
dwParam
The parameter number. See the "Remarks" section for a list of valid parameters.
pbData
The parameter data buffer. The function copies the specified parameter data to this buffer. The form of this data will vary, depending on the parameter number.
This parameter can be NULL if all you are doing is determining the number of bytes required for the returned parameter data.
pdwDataLen
The address of the parameter data length. Before calling this function, the caller should set this parameter to the length, in bytes, of the pbData buffer. Upon return, this address will contain the number of bytes of parameter data copied to the buffer.
If the buffer specified by pbData is not large enough to hold the data, the function returns the ERROR_MORE_DATA error code (through GetLastError), and stores the required buffer size, in bytes, in the variable pointed to by pdwDataLen.
If pbData is NULL, then no error is returned and the function stores the size of the data, in bytes, in the variable pointed to by pdwDataLen.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
Remarks
The dwParam value can be set to one of the following key parameter types:
HP_ALGID
The hash algorithm. The pbData buffer will contain a ALG_ID value indicating the algorithm that was specified when the hash object was created. See the CryptCreateHash function for a list of hash algorithms.
HP_HASHSIZE
The hash value size. The pbData buffer will contain a DWORD value indicating the number of bytes in the hash value. This value will usually be 16 or 20, depending on the hash algorithm.
Applications should retrieve this parameter just before the HP_HASHVAL parameter so the correct amount of memory can be allocated.
HP_HASHVAL
The hash value. The pbData buffer will contain the hash value or message digest for the hash object specified by hHash. This value is generated based on the data supplied earlier to the hash object through the CryptHashData and CryptHashSessionKey functions.
Once this parameter has been retrieved, the hash object is marked "finished" and no more data can be added to it.
Note that some CSPs may add additional parameters that can be queried through this function.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes tat prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_HASH The hash object specified by the hHash parameter is invalid. NTE_BAD_TYPE The dwParam parameter specifies an unknown parameter number. NTE_BAD_UID The CSP context that was specified when the hash was created cannot be found.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; BYTE *pbHash = NULL; DWORD dwHashLen; #define BUFFER_SIZE 256 BYTE pbBuffer[BUFFER_SIZE]; DWORD dwCount; DWORD i; // Get handle to the default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Create hash object. if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { printf("Error %x during CryptBeginHash!\n", GetLastError()); goto done; } // Fill buffer with test data. for(i = 0 ; i < BUFFER_SIZE ; i++) { pbBuffer[i] = (BYTE)i; } // Hash in buffer. if(!CryptHashData(hHash, pbBuffer, BUFFER_SIZE, 0)) { printf("Error %x during CryptHashData!\n", GetLastError()); goto done; } // Read hash value size and allocate memory. dwCount = sizeof(DWORD); if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&dwHashLen, &dwCount, 0)) { printf("Error %x during reading hash size!\n", GetLastError()); goto done; } if((pbHash = malloc(dwHashLen)) == NULL) { printf("Out of memory!\n"); goto done; } // Read hash value. if(!CryptGetHashParam(hHash, HP_HASHVAL, pbHash, &dwHashLen, 0)) { printf("Error %x during reading hash value!\n", GetLastError()); goto done; } // Print hash value. for(i = 0 ; i < dwHashLen ; i++) { printf("%2.2x ",pbHash[i]); } printf("\n"); done: // Free memory. if(pbHash !=NULL) free(pbHash); // Destroy hash object. if(hHash) CryptDestroyHash(hHash); // Release CSP handle. if(hProv) CryptReleaseContext(hProv,0);
See Also
CryptCreateHash, CryptGetKeyParam, CryptSetHashParam
The CryptHashData function is used to compute the cryptographic hash on a stream of data. This function and CryptHashSessionKey can be called multiple times to compute the hash on long streams or on discontinuous streams.
Before calling this function, the CryptCreateHash function must be called to get a handle to a hash object.
BOOL CRYPTFUNC CryptHashData(
HCRYPTHASH hHash, // in
BYTE *pbData, // in
DWORD dwDataLen, // in
DWORD dwFlags) // in
Parameters
hHash
A handle to the hash object. An application obtains this handle using the CryptCreateHash function.
pbData
The address of the data to be hashed.
dwDataLen
The number of bytes of data to be hashed. This must be zero if the CRYPT_USERDATA flag is set.
dwFlags
The flag values. The following values are currently defined:
CRYPT_USERDATA
When this flag is set, the CSP will prompt the user to input some data directly. This is then added to the hash. The application is not allowed access to the data. For example, this flag can be used to allow the user to enter a PIN into the system.
The Microsoft RSA Base Provider ignores this parameter.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The hHash handle specifies an algorithm that this CSP does not support. NTE_BAD_FLAGS The dwFlags parameter contains an invalid value. NTE_BAD_HASH The hash object specified by the hHash parameter is invalid. NTE_BAD_HASH_STATE An attempt was made to add data to a hash object that is already marked "finished." NTE_BAD_KEY A keyed hash algorithm is being used, but the session key is no longer valid. This error will be generated if the session key is destroyed before the hashing operating is complete. NTE_BAD_LEN The CRYPT_USERDATA flag is set and the dwDataLen parameter has a nonzero value. NTE_BAD_UID The CSP context that was specified when the hash object was created cannot be found. NTE_FAIL The function failed in some unexpected way. NTE_NO_MEMORY The CSP ran out of memory during the operation.
Example
See the "Example" section in the CryptSignHash function.
See Also
CryptCreateHash, CryptHashSessionKey
The CryptHashSessionKey function is used to compute the cryptographic hash on a key object. This function can be called multiple times with the same hash handle to compute the hash on multiple keys. Calls to CryptHashSessionKey can be interspersed with calls to CryptHashData.
Before calling this function the CryptCreateHash function must be called to get a handle to a hash object.
BOOL CRYPTFUNC CryptHashSessionKey(
HCRYPTHASH hHash, // in
HCRYPTKEY hKey, // in
DWORD dwFlags) // in
Parameters
hHash
A handle to the hash object. An application obtains this handle using the CryptCreateHash function.
hKey
A handle to the key object to be hashed.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The hHash handle specifies an algorithm that this CSP does not support. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_HASH The hash object specified by the hHash parameter is invalid. NTE_BAD_HASH_STATE An attempt was made to add data to a hash object that is already marked "finished." NTE_BAD_KEY A keyed hash algorithm is being used, but the session key is no longer valid. This error will be generated if the session key is destroyed before the hashing operating is complete. NTE_BAD_UID The CSP context that was specified when the hash object was created cannot be found. NTE_FAIL The function failed in some unexpected way.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; HCRYPTKEY hKey = 0; // Get handle to the default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Create hash object. if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { printf("Error %x during CryptBeginHash!\n", GetLastError()); goto done; } // Create random session key. if(!CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKey)) { printf("Error %x during CryptGenKey!\n", GetLastError()); goto done; } // Hash session key. if(!CryptHashSessionKey(hHash, hKey, 0)) { printf("Error %x during CryptHashSessionKey!\n", GetLastError()); goto done; } // Use the hash object for something. ... done: // Destroy hash object. if(hHash) CryptDestroyHash(hHash); // Destroy session key. if(hKey) CryptDestroyKey(hKey); // Release CSP handle. if(hProv) CryptReleaseContext(hProv,0);
See Also
CryptCreateHash, CryptGenKey, CryptHashData
The CryptSetHashParam function, in theory, allows applications to customize the operations of a hash object. Currently, only a single parameter is defined for this function.
BOOL CRYPTFUNC CryptSetHashParam(
HCRYPTHASH hHash, // in
DWORD dwParam, // in
BYTE *pbData, // in
DWORD dwFlags) // in
Parameters
hHash
A handle to the hash object on which to set parameters.
dwParam
The parameter number. See the "Remarks" section for a list of valid parameters.
pbData
The parameter data buffer. Place the parameter data in this buffer before calling CryptSetHashParam. The form of this data will vary, depending on the parameter number.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
Remarks
The dwParam parameter can be set to one of the following values:
HP_HASHVAL
Hash value. The pbData buffer should contain a byte array containing a hash value to place directly into the hash object. Before setting this parameter, the size of the hash value should be determined by reading the HP_HASHSIZE parameter with the CryptGetHashParam function.
Normal applications should never set this parameter. In fact, some CSPs may not even support this capability. Occasionally though, it is convenient to sign a hash value that has been generated elsewhere. This is the usual sequence of operations:
1. The application creates a hash object with CryptCreateHash.
2. It specifies a hash value by setting the HP_HASHVAL parameter.
3. It signs the hash value using CryptSignHash, obtaining a digital signature block.
Because the binding between the hashed data and the signature is fairly tenuous, no description string can be passed into CryptSignHash in this situation.
4. It destroys the hash object using CryptDestroyHash.
Note that some CSP types may add additional parameters that can be set with this function.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_BUSY The CSP context is currently being used by another process. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_FLAGS The dwFlags parameter is nonzero or the pbData buffer contains an invalid value. NTE_BAD_HASH The hash object specified by the hHash parameter is invalid. NTE_BAD_TYPE The dwParam parameter specifies an unknown parameter. NTE_BAD_UID The CSP context that was specified when the hKey key was created cannot be found. NTE_FAIL The function failed in some unexpected way.
Example
This function is used in a way similar to the CryptSetKeyParam function.
See Also
CryptCreateHash, CryptGetHashParam, CryptSetKeyParam, CryptSignHash
The CryptSignHash function is used to sign a piece of data. Because all signature algorithms are asymmetric and thus incredibly slow, CryptoAPI will not let data be signed directly. Instead, you must first hash the data and then use CryptSignHash to sign the hash value.
BOOL CRYPTFUNC CryptSignHash(
HCRYPTHASH hHash, // in
DWORD dwKeySpec, // in
LPCTSTR sDescription, // in
DWORD dwFlags, // in
BYTE *pbSignature, // out
DWORD *pdwSigLen) // in, out
Parameters
hHash
A handle to the hash object to be signed.
dwKeySpec
The key pair to use to sign the hash. The following keys can be specified:
AT_KEYEXCHANGE - Exchange private key
AT_SIGNATURE - Signature private key
The signature algorithm used is specified when the key pair was originally created.
The only signature algorithm that the Microsoft RSA Base Provider supports is the RSA Public-Key algorithm.
sDescription
The string describing the data to sign. This description text is added to the hash object before the signature is generated. Whenever the signature is authenticated (with CryptVerifySignature), the exact same description string must be supplied. This ensures that both the signer and the authenticator agree on what is being signed or authenticated.
Some CSPs (not the Microsoft RSA Base Provider) will display this description string to the user. This lets the user confirm what he or she is signing. This protects the user from unscrupulous applications and also reduces misunderstandings.
This parameter can be NULL if no description string is to included in the signature. Usually, this is only the case when the signature is performed using a signature key that is not legally bound to the user. For example, when a signature operation is performed with the key exchange private key as part of a key exchange protocol, no description string is typically specified.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
pbSignature
The buffer in which the function places the signature data.
This parameter can be NULL if all you are doing is determining the number of bytes required for the returned signature data.
pdwSigLen
The address of the signature data length. Before calling this function, the caller should set this parameter to the length, in bytes, of the pbSignature buffer. Upon return, this address will contain the number of bytes in the signature data.
If the buffer specified by pbSignature is not large enough to hold the data, the function returns the ERROR_MORE_DATA error code (through GetLastError) and stores the required buffer size, in bytes, into the variable pointed to by pdwSigLen.
If pbSignature is NULL, then no error is returned and the function stores the size of the data, in bytes, in the variable pointed to by pdwSigLen.
Remarks
Before calling this function, the CryptCreateHash function must be called to get a handle to a hash object. The CryptHashData or CryptHashSessionKey function is then used to add the data or session keys to the hash object.
Once this function has been completed, the only hash function that can be called using the hHash handle is the CryptDestroyHash function.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_ALGID The hHash handle specifies an algorithm that this CSP does not support. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_HASH The hash object specified by the hHash parameter is invalid. NTE_BAD_UID The CSP context that was specified when the hash object was created cannot be found. NTE_NO_KEY The private key specified by dwKeySpec does not exist. NTE_NO_MEMORY The CSP ran out of memory during the operation.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; #define BUFFER_SIZE 256 BYTE pbBuffer[BUFFER_SIZE]; HCRYPTHASH hHash = 0; BYTE *pbSignature = NULL; DWORD dwSigLen; LPTSTR szDescription = TEXT("Test Data"); DWORD i; // Get handle to the default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Fill buffer with test data. for(i = 0 ; i < BUFFER_SIZE ; i++) { pbBuffer[i] = (BYTE)i; } // Create hash object. if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { printf("Error %x during CryptCreateHash!\n", GetLastError()); goto done; } // Hash buffer. if(!CryptHashData(hHash, pbBuffer, BUFFER_SIZE, 0)) { printf("Error %x during CryptHashData!\n", GetLastError()); goto done; } // Determine size of signature and allocate memory. dwSigLen = 0; if(!CryptSignHash(hHash, AT_SIGNATURE, TEXT(""), 0, NULL, &dwSigLen)) { printf("Error %x during CryptSignHash!\n", GetLastError()); if(GetLastError()!=NTE_BAD_LEN) goto done; } if((pbSignature = malloc(dwSigLen)) == NULL) { printf("Out of memory!\n"); goto done; } // Sign hash object. if(!CryptSignHash(hHash, AT_SIGNATURE, szDescription, 0, pbSignature, &dwSigLen)) { printf("Error %x during CryptSignHash!\n", GetLastError()); goto done; } // Store or transmit the signature, test buffer, and description string. ... done: // Free memory used to store signature. if(pbSignature != NULL) free(pbSignature); // Destroy hash object. if(hHash != 0) CryptDestroyHash(hHash); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0);
See Also
CryptCreateHash, CryptHashData, CryptVerifySignature
The CryptVerifySignature function is used to verify a signature against a hash object.
Before calling this function, the CryptCreateHash function must be called to get a handle to a hash object. The CryptHashData and/or CryptHashSessionKey functions are then used to add the data and/or session keys to the hash object.
Once this function has been completed, the only hash function that can be called using the hHash handle is the CryptDestroyHash function.
BOOL CRYPTFUNC CryptVerifySignature(
HCRYPTHASH hHash, // in
BYTE *pbSignature, // in
DWORD dwSigLen, // in
HCRYPTKEY hPubKey, // in
LPCTSTR sDescription, // in
DWORD dwFlags) // in
Parameters
hHash
A handle to the hash object to verify against.
pbSignature
The address of the signature data to be verified.
dwSigLen
The number of bytes in the pbSignature signature data.
hPubKey
A handle to the public key to use to authenticate the signature. This public key must belong to the key pair that was originally used to create the digital signature.
sDescription
String describing the signed data. This must be exactly the same string that was passed in to the CryptSignHash function when the signature was created. If this string does not match, the signature verification will fail.
When this function is called, some CSPs (not the Microsoft RSA Base Provider) will display this description string to the user, together with an indication of whether the signature verified correctly. This provides the user with the verification results in a way that is completely independent of the application.
dwFlags
The flag values. This parameter is reserved for future use and should always be zero.
Return Value
If the function succeeds, the return value is TRUE. If it fails, the return value is FALSE. To retrieve extended error information, use the GetLastError function.
The following table lists the error codes most commonly returned by the GetLastError function. The error codes prefaced by "NTE" are generated by the particular CSP you are using.
Error Description ERROR_INVALID_HANDLE One of the parameters specifies an invalid handle. ERROR_INVALID_PARAMETER One of the parameters contains an invalid value. This is most often an illegal pointer. NTE_BAD_FLAGS The dwFlags parameter is nonzero. NTE_BAD_HASH The hash object specified by the hHash parameter is invalid. NTE_BAD_KEY The hPubKey parameter does not contain a handle to a valid public key. NTE_BAD_SIGNATURE The signature failed to verify. This could be because the data itself has changed, the description string did not match, or the wrong public key was specified by hPubKey. This error can also be returned if the hashing or signature algorithms do not match the ones used to create the signature. NTE_BAD_UID The CSP context that was specified when the hash object was created cannot be found. NTE_NO_MEMORY The CSP ran out of memory during the operation.
Example
#include <wincrypt.h> HCRYPTPROV hProv = 0; #define BUFFER_SIZE 256 BYTE pbBuffer[BUFFER_SIZE]; HCRYPTHASH hHash = 0; HCRYPTKEY hPubKey = 0; BYTE *pbSignature = NULL; DWORD dwSigLen; LPTSTR szDescription = NULL; // Get handle to the default provider. if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) { printf("Error %x during CryptAcquireContext!\n", GetLastError()); goto done; } // Load 'pbBuffer' with 'BUFFER_SIZE' bytes of test data. This must // be the same data that was originally signed. ... // Point 'pbSignature' at the signature created by a previous call // to CryptSignHash. Set 'dwSigLen' to the number of bytes in the // signature. ... // Point 'szDescription' at some text describing the data being // signed. This must be the same description text that was originally // passed to CryptSignHash. ... // Get public key of the user that created the digital signature // and import it into the CSP using CryptImportKey. This will return // a handle to the public key in 'hPubKey'. ... // Create hash object. if(!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { printf("Error %x during CryptCreateHash!\n", GetLastError()); goto done; } // Hash buffer. if(!CryptHashData(hHash, pbBuffer, BUFFER_SIZE, 0)) { printf("Error %x during CryptHashData!\n", GetLastError()); goto done; } // Validate digital signature. if(!CryptVerifySignature(hHash, pbSignature, dwSigLen, hPubKey, szDescription, 0)) { if(GetLastError() == NTE_BAD_SIGNATURE) { printf("Signature failed to validate!\n"); } else { printf("Error %x during CryptSignHash!\n", GetLastError()); } } else { printf("Signature validated OK\n"); } done: ... // Release public key. if(hPubKey != 0) CryptDestroyKey(hPubKey); // Destroy hash object. if(hHash != 0) CryptDestroyHash(hHash); // Release provider handle. if(hProv != 0) CryptReleaseContext(hProv, 0);
See Also
CryptCreateHash, CryptHashData, CryptSignHash
CHAPTER 13
This section describes some of the data types and constants that are used by the functions in CryptoAPI.
The ALG_ID data type is used to specify algorithm identifiers. Parameters of this data type are passed to most of the functions in CryptoAPI. This data type is defined in the WINCRYPT.H header file as:
typedef unsigned int ALG_ID;
The following table lists the algorithm identifiers that are currently defined. Authors of custom CSPs can define new values.
Constant Description CALG_MD2 * MD2 hashing algorithm CALG_MD4 MD4 hashing algorithm CALG_MD5 * MD5 hashing algorithm CALG_SHA * SHA hashing algorithm CALG_MAC * MAC keyed hash algorithm CALG_RSA_SIGN * RSA public-key signature algorithm CALG_DSS_SIGN DSA public-key signature algorithm CALG_RSA_KEYX * RSA public-key key exchange algorithm CALG_DES DES encryption algorithm CALG_RC2 * RC2 block encryption algorithm CALG_RC4 * RC4 stream encryption algorithm CALG_SEAL SEAL encryption algorithm
The algorithms with an asterisk (*) are supported by the Microsoft RSA Base Provider.
The HCRYPTHASH data type is used to represent handles to a hash object. These handles are used to indicate to the CSP module which hash is being used in a particular operation. The CSP module does not allow direct manipulation of the hash values. Instead, the user manipulates the hash values through the hash handle.
HCRYPTHASH is defined in the WINCRYPT.H header file as:
typedef unsigned long HCRYPTHASH;
The HCRYPTKEY data type is used to represent handles to cryptographic keys. These handles are used to indicate to the CSP module which key is being used in a specific operation. The CSP module does not allow direct access to the key values. Instead, the user performs functions using the key value through the key handle.
HCRYPTKEY is defined in the WINCRYPT.H header file as:
typedef unsigned long HCRYPTKEY;
The HCRYPTPROV data type is used to represent handles to CSPs. These handles are used to indicate which CSP module should perform the specific operation.
HCRYPTPROV is defined in the WINCRYPT.H header file as:
typedef unsigned long HCRYPTPROV;
MAXUIDLEN is a numeric constant that specifies the maximum size for CSP names and key container names. No CSP or key container name can be longer than MAXUIDLEN characters, including the terminating zero.
MAXUIDLEN is defined in the WINCRYPT.H header file as:
#define MAXUIDLEN 64
MS_DEF_PROV is a string constant set to the name of the Microsoft RSA Base Provider. This constant is used with the CryptAcquireContext CryptSetProvider functions.
MS_DEF_PROV is defined in the WINCRYPT.H header file as:
#define MS_DEF_PROV TEXT("Microsoft Base Cryptographic Provider v1.0")