Diffie-Hellman

Diffie-Hellman is a key agreement algorithm which allows two parties to establish a secure communications channel. The original Diffie-Hellman is an anonymous protocol meaning it is not authenticated, so it is vulnerable to man-in-the-middle attacks. Crypto++ exposes the unauthenticated protocol through DH classes. Extensions to the original Diffie-Hellman includes authentication which hardens the exchange protocol against many man-in-the-middle attacks. The authenticated version of Diffie-Hellman is usually referred to as Unified Diffie-Hellman. Crypto++ offers the Unified Diffie-Hellman through its DH2 classes.

A number of standard bodies have Diffie-Hellman implementations, including RFC 2631, Diffie-Hellman Key Agreement Method, ANSI X9.42, Agreement Of Symmetric Keys Using Diffie-Hellman and MQV Algorithms, and IEEE P1363, Standard Specifications for Public Key Cryptography, Annex D. Each implementation is slightly different and might not interoperate.

When data is in motion, key exchange and transport is usually the starting point. Unfortunately, exchange and transport are often an after thought in a project, especially if a resident cryptography enthusiast or cryptographer is not available. The result can sometimes be a weak system, where an adversary can recover a session key during channel setup due to a weak or flawed exchange and transport (why attack AES directly when its easier to attack a flawed exchange?).

The examples below use a 1024-bit modulus to speed up computation and keep the output manageable. As with asymmetric keys, symmetric ciphers, hashes, and MACs, an appropriate security level should be chosen. This typically means transporting an AES-128 bit key using a prime group with a minimum of 3072-bits, or an elliptic curve over a prime field with a minimum size of 256 bits.

Sample Programs

The first two examples generate Diffie-Hellman parameters and load standardized parameters (that is, initializes a Crypto++ object with standard parameters). The standard parameters are usually provided by bodies such as ANSI, IEEE, IETF, and NIST.

Details Lost in Generation and Initialization discusses security levels which are not readily apparent. Its the sort of thing that can subtly make a system insecure, even though the system was built from secure components. Though the topic should be presented first, it is visited after the first two examples so there is a frame of reference.

Generating Parameters (MODP)

Crypto++ generates a safe prime for use in Diffie-Hellman. Generating safe primes can be time consuming. It is not uncommon to experience generations times that appear like a program hang. For those who are interested, rsa_kgen_prof.zip demonstrates how to instrument code to track the generation time using the ThreadUserTimer class.

The example below creates the classical parameters p and g. The program then verifies the prime and generator, calculates and verifies q, and prints the order of the subgroup. Most of the interesting work is performed in nbtheory.cpp using PrimeAndGenerator::Generate. The full program is available in dh-param.zip.

Parameter Initialization (MODP)

Often times, key exchange will occur using standardized parameters. For example, RFC 3526 (More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)) and RFC 5114 (Additional Diffie-Hellman Groups for Use with IETF Standards). The standards will provide either {p,g} or {p,q,g}. p and g are the customary prime and generator. q is the order of the subgroup and due to Schnorr.

The example below is available in dh-init.zip, and loads RFC 5114's 1024-bit MODP group specified in section 2.1. Rather than calling GenerateRandomWithKeySize, a call is made to Initialize since p, q, and g are available.

Note that we do not enforce a safe prime. The parameters were selected by the IETF to provide the stated security level, which reduces the number of modular multiplications to increase efficiency.

In Generating Parameters (MODP), Crypto++ generated a safe prime. A safe prime is of the form p = 2q + 1, with p and q prime. The safe prime p offers a subgroup nearly the size of the modulus. That is, a 1024-bit prime usually produces a subgroup of 1023-bits. The order of the subgroup means the size of the data or secret (in bits) can be in the interval [2, 1023]. Since there are no free lunches, this type of Diffie-Hellman group will incur 1023 square and multiply operations during exponentiation.

In contrast, Parameter Initialization (MODP) does not use a safe prime so the size of the data or secret (in bits) is reduced. The primes are of the form p = qr + 1, with p, q prime (this is known a Schnorr Group). From the example above, the IETF provided parameters with a subgroup order of 160 bits. This means the data or secret can be in the range [2, 160] bits. The reduced subgroup order has an upside: the algorithm will only need to perform 160 square and multiply operations, which improves efficiency.

While efficiency is an important design consideration, cryptography must first be secure. There are two known instance problems or attacks on Diffie-Hellman: the first attack is used against classical discrete logarithms (logarithms over a finite field) and is often Index calculus, modified Pollard's rho, or Baby-step giant-step. The methods run in roughly sub-exponential time (but not polynomial time). The second attack, due to Pohlig and Hellman, attempts to compromise the system by taking advantage of structure of cyclic subgroups. That is, attacks are mounted against the parameter q. The Pohlig-Hellman algorithm runs in square root time.

Here is the take-away by example: suppose you need to exchange an AES-128 key for use with bulk encryption. Based on NIST security levels, Table 1: Comparable Algorithm Strengths (reading from the FF or finite field column), you need at least a 3072-bit prime modulus and a prime subgroup order of at least 256 bits. The 3072-bit modulus resists the sub-exponential attacks against discrete logarithms, while the 256-bit subgroup provides the necessary security against Pohlig-Hellman.

If you use a safe prime, you waste cycles since there will be approximately 3072 squares and multiplies. You cannot use a RFC 5114 MODP group since the RFC does not offer a '3072-bit MODP Group with 256-bit Prime Order Subgroup'. You would need to (1) move to elliptic curves and use RFC 5114's '256-bit Random ECP Group', (2) accept the wasted cycles, or (3) find a suitable alternative.

Validating Parameters

Any time parameters are loaded from an outside source, the parameters must be validated. The parameters could have been poorly generated by the other party (for example, a random prime rather than a safe prime), or the parameters could be have been manipulated by an adversary (such as a middle man). If validation fails, immediately abort further processing.

Below, validation has been added to dh-init.exe. The call of interest is ValidateGroup, which takes a RandomNumberGenerator and int specifying a level. Validation can be performed on Crypto++ generated parameters and should be called on parameters received from a peer.

We perform one additional test: gq ≡ 1 mod p, which is the call to ModularExponentiation(g, q, p). The test verifies that g has order q since its not readily apparent that Crypto++ performs the test in ValidateGroup. See DH Parameter Generation and Confinement Attacks.

When tracing validation over the integers, we land in DL_GroupParameters_IntegerBased from gfpcrypt.cpp.

Finally, VerifyPrime from nbtheory.cpp is shown below. By default, Crypto++ calls Rabin-Miller 10 times. If the iteration count seems too low, call RabinMillerTest directly. Tracing the calls to IsPrime and RabinMillerTest to uncover the library's terminal tests are left as an exercise.

Key Agreement and Transport

After the previous examples, we are finally ready to perform key exchange and arrive at a shared secret. Though Diffie-Hellman is most often described as simply gab, the protocol (and Crypto++) uses a two part, asymmetric key. The private keys are the exponents a and b; while ga and gb are the public keys.

There are two examples of key agreement available. The first uses classical unauthenticated Diffie-Hellman (sometimes referred to as DH1), and the second demonstrates authenticated Diffie-Hellman (referred to as DH2). Authenticated Diffie-Hellman is also known as Unified Diffie-Hellman. Classical Diffie-Hellman uses one key pair from each party during exchange; while unified Diffie-Hellman requires two key pairs from each party during agreement. In the unified model, one key pair is used for classical Diffie-Hellman and the second pair is used for authenticity assurances (that is, signatures).

The shared secret is sometimes called a key encryption key or KEK. This is because the shared secret is typically not used for bulk encryption. The KEK is used to transport the actual symmetric cipher key that will be used for bulk encryption. In discussions of Diffie-Hellman, the symmetric key to be transported by the KEK is sometimes called the content encryption key or CEK. Taking from RFC 2631, Diffie-Hellman Key Agreement Method:

Diffie-Hellman key agreement requires that both the sender and recipient of a message have key pairs. By combining one's private key and the other party's public key, both parties can compute the same shared secret number. This number can then be converted into cryptographic keying material. That keying material is typically used as a key-encryption key (KEK) to encrypt (wrap) a content encryption key (CEK) which is in turn used to encrypt the message data.

Key Agreement (Unauthenticated)

The key exchange below derives a shared secret between the two parties, A and B, using the DH class. If the parties are under attack, three or more parties might be participating (and the original parties would be no wiser).

Each party calls Agree, which combines their private key with the other's public key. For those who referenced the library's test code SimpleKeyAgreementValidate in validate2.cpp, the code below should look familiar since its basically SimpleKeyAgreementValidate. Finally, The sample below is available in dh-agree.zip.

The code above also uses VerifyBufsEqual, which provides a constant time memory comparison to avoid leaking timing information.

Key Agreement (Authenticated)

Unified Diffie-Hellman, sometimes known as Ephemeral Unified Model Scheme (X9.63) or Static Unified Model (NIST), derives a shared secret between the two parties, A and B, using the DH2 class. Tampering with the exchange will be detected by the parties via authenticity assurances on the exchanged parameters.

Details of Unified Diffie-Hellman can be found in IEEE's P1363, ANSI X9.63, and NIST's SP 800-56. In the unified model, each party holds two key pairs: one key pair is used to perform classical (unautheticated) Diffie-Hellman, and the second pair is used for signing to ensure authenticity. The classical key pair is called ephemeral in Unified Diffie-Hellman since it is a temporary key pair used only for the current exchange. The signing key pair is the static pair. The public portion of the signing key can be published in a common directory for convenient access since the signing key pair changes infrequently.

Each party calls Agree, which combines their private keys with the other's publics keys. For those who referenced the library's test code AuthenticatedKeyAgreementValidate in validate2.cpp, the code below should look familiar since its basically AuthenticatedKeyAgreementValidate. Finally, The sample below is available in dh-unified.zip.

In production, the test in VerifyBufsEqualp cannot be performed since the values will be on different hosts. A problem with agreement will not be detected until data starts flowing - the first data packet received with not authenticate.

Previous Wiki Page

Generating a secret key

The following example illustrates how two hosts, Alice and Bob, agree on a shared secret key using Diffie-Hellman key exchange.

Alice generates a prime and base which she shares with Bob. (The prime and base can also simply be hard coded into the application.) Alice then generates a pair of public and private integers. The public part is shared with Bob.

Bob uses the prime and base that he received from Alice to generate a pair of public and private integers. The public part is shared with Alice.

//////////////////////////////////////////////////////////////////////////
// Alice
// Initialize the Diffie-Hellman class with a random prime and base
AutoSeededRandomPool rngA;
DH dhA(rngA, 128);
// Extract the prime and base. These values could also have been hard coded
// in the application
Integer iPrime = dhA.GetGroupParameters().GetModulus();
Integer iGenerator = dhA.GetGroupParameters().GetSubgroupGenerator();
SecByteBlock privA(dhA.PrivateKeyLength());
SecByteBlock pubA(dhA.PublicKeyLength());
SecByteBlock secretKeyA(dhA.AgreedValueLength());
// Generate a pair of integers for Alice. The public integer is forwarded to Bob.
dhA.GenerateKeyPair(rngA, privA, pubA);
//////////////////////////////////////////////////////////////////////////
// Bob
AutoSeededRandomPool rngB;
// Initialize the Diffie-Hellman class with the prime and base that Alice generated.
DH dhB(iPrime, iGenerator);
SecByteBlock privB(dhB.PrivateKeyLength());
SecByteBlock pubB(dhB.PublicKeyLength());
SecByteBlock secretKeyB(dhB.AgreedValueLength());
// Generate a pair of integers for Bob. The public integer is forwarded to Alice.
dhB.GenerateKeyPair(rngB, privB, pubB);
//////////////////////////////////////////////////////////////////////////
// Agreement
// Alice calculates the secret key based on her private integer as well as the
// public integer she received from Bob.
if (!dhA.Agree(secretKeyA, privA, pubB))
return false;
// Bob calculates the secret key based on his private integer as well as the
// public integer he received from Alice.
if (!dhB.Agree(secretKeyB, privB, pubA))
return false;
// Just a validation check. Did Alice and Bob agree on the same secret key?
if (VerifyBufsEqualp(secretKeyA.begin(), secretKeyB.begin(), dhA.AgreedValueLength()))
return false;
return true;

Using Diffie-Hellman to generate an AES key

Building on the previous example. Note that the example uses in-place encryption and decryption where the input and output buffers are identical: