Virtual Developer Workshop: Containerized Development with Docker
For long-term Visual C++ developers, the CryptoAPI will be a familiar part of the programming toolkit, providing hashing, encryption, signing, and certificate management since the very first releases of the Win32 SDK. After 12 years of service, the replacement for the CryptoAPI has been released with Windows Vista: Windows Cryptography API: Next Generation (CNG)
The Cryptography API: Next Generation (CNG) brings two main advantages over the CryptoAPI technologies that it replaces: better API factoring to allow the same functions to work using a wide range of cryptographic algorithms, and the inclusion of a number of newer algorithms that are part of the National Security Agency (NSA) Suite B. Suite B algorithms meet the requirements for the transmittal of information up to a classification level of Top Secret, and are an important requirement for companies that produce software for use by the government. In this article, Elliptic Curve Diffie-Hellman (ECDH), which is one of the newer secret agreement algorithms contained in the CNG, will be demonstrated.
Before diving into CNG, it is worth briefly looking at the bigger picture. Like the CryptoAPI, CNG ships in the box with the Windows operating system, with availability limited to Windows Vista and above. CNG is targeted at C++ developers, with C-DLL interoperability the only way for a C# or VB.NET developer to get access to its functionality. CNG does not suffer from the same export restrictions that hampered CryptoAPI adoption in its early releases. CNG works in both user and kernel mode, and also supports all of the algorithms from the CryptoAPI, which greatly reduces migration difficulties. The CNG is fully factorable, and any of the functionality it offers can be extended or replaced by third-party cryptography providers. The Microsoft provider that implements CNG is housed in Bcrypt.dll.
In addition to the secret agreement functionality demonstrated later in this article, CNG also offers functionality in the following areas:
- Random number generation
- Digital Signing
- Asymmetric encryption
- Symmetric encryption
Most developers will be familiar with the difference between symmetric encryption (also known as private-key cryptography) that uses a shared secret to secure data, and asymmetric encryption (also known as public-key cryptography) that uses a public-private key pair to secure data. Asymmetric encryption has a significantly higher computation overhead, and many high-level protocols will use asymmetric to exchange a shared secret that then can be used with symmetric encryption over the life of a session. The SSL-handshake is an everyday example where this key exchange occurs.
Well-known asymmetric algorithms, such as the RSA algorithm, use the product of two large prime numbers as the public key, as it is difficult to work out the original prime numbers (which are the private key) that were used to calculate the product. Advances in both mathematical theory and computing power have led to significant increases in the size of keys needed to securely use asymmetric encryption based on prime numbers. Elliptic curve cryptography (ECC) is a different approach that relies on the co-ordinates of points over an elliptical curve. This article won't delve into the cryptographic theory, but interested readers are encouraged to visit Wikipedia for a full explanation (http://en.wikipedia.org/wiki/Elliptic_curve_cryptography). The main advantage of ECC over algorithms such as RSA is that shorter key lengths can be used to achieve the same level of security.
The current state of the CNG's documentation is fairly immature, and the SDK samples don't contain any CNG code samples. Because the interface into the CNG has been factored to work across a wide variety of algorithms and cryptography tasks, trying to work out the order in which to call the various functions can be a difficult task. To supplement MSDN's documentation on typical CNG programming tasks, which does not include the pseudo-code for secret exchange, the rest of the article will focus on this topic.
The first CNG task is to open an algorithm provider that supports secret exchange. When an algorithm provider is opened, the provider and algorithm need to be specified—to open the Microsoft provider using the 384-bit prime elliptic curve Diffie-Hellman key exchange algorithm, the code would read:
BCRYPT_ALG_HANDLE algHandle; BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_ECDH_P384_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
Once an algorithm provider is open, a key pair needs to be generated or opened from a store. For the generation case, the code is:
BCRYPT_KEY_HANDLE fullKey; BCryptGenerateKeyPair(algHandle, &fullKey, 384, 0); BCryptFinalizeKeyPair(fullKey, 0);
The public key then needs to be exported and passed to the other side of the secret exchange:
PUCHAR publicKey; ULONG publicKeySize = 0; //get the size of the public key BCryptExportKey(fullKey, NULL, BCRYPT_ECCPUBLIC_BLOB, NULL, 0, &publicKeySize, 0); //allocate a buffer for the public key publicKey = new UCHAR[publicKeySize]; //get the public key BCryptExportKey(fullKey, NULL, BCRYPT_ECCPUBLIC_BLOB, publicKey, publicKeySize, &publicKeySize, 0); //now we can pass publicKey to partner over unsecure transport channel
The parties now exchange public keys, and the public key of the external party is imported and used to create a secret handle.
//get these two bits of data for the other side //of the secret exchange PUCHAR publicKeyFromOtherParty; ULONG publicKeyFromOtherPartySize; //import the other parties public key BCRYPT_KEY_HANDLE importedPublicKey; BCryptImportKeyPair(algHandle, NULL, BCRYPT_ECCPUBLIC_BLOB, &importedPublicKey, publicKeyFromOtherParty, publicKeyFromOtherPartySize, 0); //create the secret BCRYPT_SECRET_HANDLE secretHandle; BCryptSecretAgreement(fullKey, importedPublicKey, &secretHandle, 0);
Once the secret handle has been generated, a symmetric key can be derived. It will be the same for both parties that have exchanged public keys:
PUCHAR derivedSymmetricKey; ULONG derivedKeySize; //get the size of the derived key BCryptDeriveKey(secretHandle, L"HASH", NULL, NULL, 0, &derivedKeySize, 0); //allocate a buffer for the derived key derivedSymmetricKey = new UCHAR[derivedKeySize]; //get the derived key BCryptDeriveKey(secretHandle, L"HASH", NULL, derivedSymmetricKey, derivedKeySize, &derivedKeySize, 0);
With the derived symmetric key in place, the parties can exchange encrypted messages with no performance issues.
As this code shows, once it is known which CNG functions need to be called in which order, the actual code to complete a given cryptographic task is fairly easy. Even once error handling and memory clean-up has been added to the code contained in this article, the entire programming task still fits within a function of only a few dozen lines. Implementing hashing, encryption, and random number generation are also fairly quick in terms of lines-of-code count. If a system is being developed that will be targeted to Windows Vista or Windows Server 2008 exclusively, preferring CNG over the CryptoAPI makes a lot of sense.