Deriving the Bulk Encryption and MAC Keys

The bulk encryption and MAC keys are mostly derived from the master key, but often include a number of other items, depending on the protocol and cipher suite used.

The process is the same for both client and server:

  1. The protocol engine calls CryptSetKeyParam on the master key one or more times, such that the CSP has all of the information necessary to build the keys.
  2. A hash object is created from the master key via CryptCreateHash. This step is necessary because it is not possible to derive CryptoAPI keys directly from other keys.
  3. The two bulk encryption keys and the two MAC keys are created from the "master hash" object, via four calls to CryptDeriveKey.

Note  When performing SSL "reconnects", the protocol engine may perform the above procedure several times, using the same master key. This enables the client and server to have multiple (often simultaneous) connections, each using different bulk encryption and MAC keys, without doing any RSA operations.

As with all CSPs, it is important that good thread-safe practices are used. Thread counts of several dozen are not unusual.

The protocol engine's source code is typically;

BOOL fClient = <are we a client?>;
CRYPT_DATA_BLOB Data;
HCRYPTHASH hMasterHash;

// Finish creating the master_secret.
switch(<protocol being used>)
{
    case <PCT 1.0>:
        // Specify clear key value.
        Data.pbData = pClearKey;
        Data.cbData = cbClearKey;
        CryptSetKeyParam(hMasterKey, KP_CLEAR_KEY, 
                         (PBYTE)&Data, 0);

        // Specify the CH_CHALLENGE_DATA.
        Data.pbData = pChallenge;
        Data.cbData = cbChallenge;
        CryptSetKeyParam(hMasterKey, KP_CLIENT_RANDOM, 
                         (PBYTE)&Data, 0);

        // Specify the SH_CONNECTION_ID_DATA.
        Data.pbData = pConnectionID;
        Data.cbData = cbConnectionID;
        CryptSetKeyParam(hMasterKey, KP_SERVER_RANDOM, 
                         (PBYTE)&Data, 0);

        // Specify the SH_CERTIFICATE_DATA.
        Data.pbData = pbServerCertificate;
        Data.cbData = cbServerCertificate;
        CryptSetKeyParam(hMasterKey, KP_CERTIFICATE, 
                         (PBYTE)&Data, 0);
        break;

    case <SSL 2.0>:

        // Specify clear key value.
        Data.pbData = pClearKey;
        Data.cbData = cbClearKey;
        CryptSetKeyParam(hMasterKey, KP_CLEAR_KEY, 
                         (PBYTE)&Data, 0);
        // Specify the CH_CHALLENGE_DATA.
        Data.pbData = pChallenge;
        Data.cbData = cbChallenge;
        CryptSetKeyParam(hMasterKey, KP_CLIENT_RANDOM,
                         (BYTE*)&Data, 0);

        // Specify the SH_CONNECTION_ID_DATA.
        Data.pbData = pConnectionID;
        Data.cbData = cbConnectionID;
        CryptSetKeyParam(hMasterKey, KP_SERVER_RANDOM,
                         (BYTE*)&Data, 0);

        break;

case <SSL 3.0>:
case <TLS 1.0>:

        // Specify client_random.
        Data.pbData = pClientRandom;
        Data.cbData = cbClientRandom;
        CryptSetKeyParam(hMasterKey, KP_CLIENT_RANDOM, 
                         (PBYTE)&Data, 0);

        // Specify server_random.
        Data.pbData = pServerRandom;
        Data.cbData = cbServerRandom;
        CryptSetKeyParam(hMasterKey, KP_SERVER_RANDOM, 
                         (PBYTE)&Data, 0);
}

// Create the master hash object from the master key.
CryptCreateHash(hProv, CALG_SCHANNEL_MASTER_HASH,
                hMasterKey, 0, &hMasterHash);

// Derive read key from the master hash object.
CryptDeriveKey(hProv, 
               CALG_SCHANNEL_ENC_KEY, 
               hMasterHash,
               fClient ? CRYPT_SERVER : 0,
               &hReadKey);

// Derive write key from the master hash object.
CryptDeriveKey(hProv,
               CALG_SCHANNEL_ENC_KEY,
               hMasterHash,
               fClient ? 0 : CRYPT_SERVER,
               &hWriteKey);

if(<protocol being used> != <SSL 2.0>)
{
    // Derive read MAC from the master hash object.
    CryptDeriveKey(hProv,
                   CALG_SCHANNEL_MAC_KEY,
                   hMasterHash,
                   fClient ? CRYPT_SERVER : 0,
                   &hReadMAC);

    // Derive write MAC from the master hash object.
    CryptDeriveKey(hProv,
                   CALG_SCHANNEL_MAC_KEY,
                   hMasterHash,
                   fClient ? 0 : CRYPT_SERVER,
                   &hWriteMAC);
}

CryptDestroyHash(hMasterHash);