PHD Computer Consultants Ltd
... Setting the RC4 key in Windows CryptoAPI
Mian text: 19 June 2001
Last modified: 3 June 2003.

This is a technical article for Windows Cryptography programmers

Introduction

This article shows how to set the RC4 key used by the Microsoft Windows CryptoAPI Win32 routines to do encryption or decryption.  Surprisingly there is no explicit way to set a known key.  Instead, a key must be encrypted by a second key before it can be used.  This article shows how to get round this restriction by using "do-nothing" key as this second key.

Thanks to Greg Stark who pointed me at the Microsoft article that provided the basis of the problem solution, and gave further advice:

HOWTO: Export/Import Plain Text Session Key Using CryptoAPI
http://support.microsoft.com/support/kb/articles/Q228/7/86.ASP

We wanted to set the RC4 key so that we could encrypt and decrypt Adobe PDF files.  The Portable Document Format (PDF) is Copyright © Adobe™ Systems Incorporated.  Most PDF files use a 40 bit encryption technique; CryptoAPI can be used for encryption without having to set an RC4 key explicitly.  However the new Acrobat 5 PDF 1.4 format 128 bit encryption uses an algorithm that requires RC4 keys to be set explicitly.

Background

RC4 is an algorithm for encrypting data streams.  Various key lengths can be used, usually in the range of 40 bits to 128 bits.  It is symmetric because encrypted data can be decoded by encrypting again with the same key.  It is a stream cipher because it serially encrypts data, one bit at a time.  Its key is called a session key because it is a symmetric encryption algorithm where the same key is used for both encryption and decryption.

RC4 was invented by RSA Security Inc.  They have not patented the algorithm but still claim it as a trade secret even though the algorithm is now well known.  If you want to use RC4 then it might be safest legally to licence its use from RSA, or - our approach - use a facility such as Windows CryptoAPI that presumably is legally above board.

Note that CryptoAPI has a key length limitation that you should be aware of.  Key lengths of 128 bits could not be exported from the USA until relatively recently.  And France has further encryption restrictions on older Windows platforms.
Windows Me should be 128 bits for most of the world.  Microsoft has a free update to 128 bits for most of the world for Windows 2000: http://www.microsoft.com/windows2000/downloads/recommended/encryption/default.asp.  This link updates Internet Explorer to 128 bits: http://www.microsoft.com/windows/ie/download/128bit/intro.htm.  Also see http://www.microsoft.com/security/ and http://www.microsoft.com/exporting/.

CryptoAPI basics

To start using CryptoAPI, call CryptAcquireContext to get a handle to a Cryptographic Service Provider (CSP).  When done, call CryptReleaseContext.

Various CryptoAPI routines can be used to generate a hash - a short fixed size digest of a longer message.  CryptCreateHash is used to create a hash object.  Data is typically added to the hash using CryptHashData.  If the actual hash value is required then call CryptGetHashParam with a HP_HASHVAL parameter.  When done, destroy the hash using CryptDestroyHash.

To use RC4, you can obtain a key in three ways: generate a random key using CryptGenKey, derive a key from a hash using CryptDeriveKey, or import a key using CryptImportKey.  Then encrypt or decrypt data using CryptEncrypt or CryptDecrypt.  When done, destroy the key using CryptDestroyKey.

Setting a CryptoAPI key

None of the three ways of initialising an RC4 key explicitly let you set the key.  However the key can in fact be set using CryptImportKey using a "do-nothing" key.

When using CryptImportKey, the key is imported from a "blob" - an encrypted data structure containing the key.  If we tell CryptImportKey to use a "do-nothing" encryption key, then the key in the blob is unchanged by the import process.

For this solution to work, we need a "do-nothing" key, and we need to know the format of a "blob".  The Microsoft article Q228786 describes a do-nothing "exponent-of-one" keypair; both the public and private keys of the keypair have an exponent of one.  (The encryption algorithm takes the data and raises it to a power.  As you may recall from your math, (x)^1 = x.  This is also true for the mod N arithmetic RSA uses.)

There are several blob formats used by Microsoft.  The SIMPLEBLOB is reasonably well defined in the Microsoft Platform SDK documentation.  It is a 76 byte (0x4C) structure starting with a 12 byte BLOBHEADER etc, followed by the reversed key, a zero value, random non-zero padding data and ending with some constants.

Example code

The following code illustrates how to set the RC4 key to a known value.  The key is then used to encrypt and decrypt a data stream.

The code first declares two static BYTE arrays that contain the keys we need.  PrivateKeyWithExponentOfOne is a PRIVATEKEYBLOB structure taken from the Microsoft article.  SimpleBlobRC4KeyTemplate is a SIMPLEBLOB structure template that we update with our known RC4 key.

After getting a handle to a CSP using CryptAcquireContext, the do-nothing "exponent-of-one" keypair is imported from PrivateKeyWithExponentOfOne using CryptImportKey.  Next, the known RC4 key is inserted into SimpleBlobRC4KeyTemplate, not forgetting to reverse it.  The RC4 key is imported using CryptImportKey again.  Finally, the RC4 key is used to encrypt/decrypt some data using CryptEncrypt.  The keys are destroyed using CryptDestroyKey and the CSP released using CryptReleaseContext.

Here are links to the source code and the VC++ 5 compiled ANSI release executable console application.  The example needs (MS Enhanced Crypto Service Provider) 128 bit encryption to work and will only work with a 16 byte (128 bit) RC4 key; it will not work with smaller key lengths. However if you only have or select the MS Basic Crypto Service Provider then the code will work (and only work) with 5 byte (40 bit) keys. The code does not check that the provider and key length are compatible. (Thanks to Steve Morley for the key length information.)

#include <windows.h>
#include <stdio.h>
#include <wincrypt.h>

///////////////////////////////////////////////////////////////////////////////
//    Setting an RC4 key is only possible using a trick from Microsoft Q228786.
//    This is a PRIVATEKEYBLOB blob with an exponent of one keypair,
//    ie it allows keys to be exported/imported without changing the key data.
//    This is needed so that the following simple blob can be imported.

static BYTE PrivateKeyWithExponentOfOne[] =
{
    0x07, 0x02, 0x00, 0x00, 0x00, 0xA4, 0x00, 0x00,
    0x52, 0x53, 0x41, 0x32, 0x00, 0x02, 0x00, 0x00,
    0x01, 0x00, 0x00, 0x00, 0xAB, 0xEF, 0xFA, 0xC6,
    0x7D, 0xE8, 0xDE, 0xFB, 0x68, 0x38, 0x09, 0x92,
    0xD9, 0x42, 0x7E, 0x6B, 0x89, 0x9E, 0x21, 0xD7,
    0x52, 0x1C, 0x99, 0x3C, 0x17, 0x48, 0x4E, 0x3A,
    0x44, 0x02, 0xF2, 0xFA, 0x74, 0x57, 0xDA, 0xE4,
    0xD3, 0xC0, 0x35, 0x67, 0xFA, 0x6E, 0xDF, 0x78,
    0x4C, 0x75, 0x35, 0x1C, 0xA0, 0x74, 0x49, 0xE3,
    0x20, 0x13, 0x71, 0x35, 0x65, 0xDF, 0x12, 0x20,
    0xF5, 0xF5, 0xF5, 0xC1, 0xED, 0x5C, 0x91, 0x36,
    0x75, 0xB0, 0xA9, 0x9C, 0x04, 0xDB, 0x0C, 0x8C,
    0xBF, 0x99, 0x75, 0x13, 0x7E, 0x87, 0x80, 0x4B,
    0x71, 0x94, 0xB8, 0x00, 0xA0, 0x7D, 0xB7, 0x53,
    0xDD, 0x20, 0x63, 0xEE, 0xF7, 0x83, 0x41, 0xFE,
    0x16, 0xA7, 0x6E, 0xDF, 0x21, 0x7D, 0x76, 0xC0,
    0x85, 0xD5, 0x65, 0x7F, 0x00, 0x23, 0x57, 0x45,
    0x52, 0x02, 0x9D, 0xEA, 0x69, 0xAC, 0x1F, 0xFD,
    0x3F, 0x8C, 0x4A, 0xD0,

    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x64, 0xD5, 0xAA, 0xB1,
    0xA6, 0x03, 0x18, 0x92, 0x03, 0xAA, 0x31, 0x2E,
    0x48, 0x4B, 0x65, 0x20, 0x99, 0xCD, 0xC6, 0x0C,
    0x15, 0x0C, 0xBF, 0x3E, 0xFF, 0x78, 0x95, 0x67,
    0xB1, 0x74, 0x5B, 0x60,

    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

///////////////////////////////////////////////////////////////////////////////
//    This is a simple blob that contains an unencrypted RC4 key,
//    when used in conjunction with the exponent-of-one-key above.
//    To use our own RC4 key splat it in at offset 0x0C in reverse

static BYTE SimpleBlobRC4KeyTemplate[] =
{   0x01, 0x02, 0x00, 0x00,        // BLOBHEADER bType, bVersion, reserved
    0x01, 0x68, 0x00, 0x00,        // BLOBHEADER aiKeyAlg: CALG_RC4
    0x00, 0xA4, 0x00, 0x00,        // algid used to encrypt blob: CALG_RSA_KEYX
                                   // Rest is a PKCS #1, type 2 encryption block:
                                   // For MS Base CP this is always 512 bits (64 bytes)
    0x0F, 0x0E, 0x0D, 0x0C,        // Key Material: 16 byte actual RC4 key goes in reverse from here
    0x0B, 0x0A, 0x09, 0x08,
    0x07, 0x06, 0x05, 0x04,
    0x03, 0x02, 0x01, 0x00,        // to here
    0x00, 0x3D, 0xB5, 0xE1,        // Zero then Random non-zero padding..
    0x5B, 0x27, 0x13, 0x36,
    0x69, 0x9B, 0x56, 0xA9,
    0x52, 0x98, 0x5B, 0xA9,
    0x17, 0x24, 0x1D, 0x1A,
    0x2B, 0x9C, 0xE7, 0x35,
    0x3C, 0xC9, 0xD6, 0xE1,
    0xD7, 0x70, 0xCC, 0x70,
    0x94, 0x6B, 0x90, 0xD0,
    0x7E, 0x92, 0x2E, 0x5C,
    0x80, 0xDB, 0xE5, 0x2D,
    0x60, 0x75,                    // ..Padding
    0x02, 0x00,                    // Block type; reserved
};

///////////////////////////////////////////////////////////////////////////////
//    EncryptData:    Sets an RC4 key and encrypts the given data
//                    (Tidy up to release handles in error cases)

bool EncryptData( BYTE* key, int keylen, BYTE* Data, DWORD* pDataLen)
{
    // Check key lengths
    if( keylen<=0 || keylen>16)
        return false;

    // Get a CSP handle
    HCRYPTPROV CryptoProv = NULL;
    if( !CryptAcquireContext(&CryptoProv, NULL, NULL, PROV_RSA_FULL, 0))
        if (!CryptAcquireContext(&CryptoProv, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))
            if (!CryptAcquireContext(&CryptoProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
                return false;
    
    // Import do-nothing exponent-of-one keypair
    HCRYPTKEY ExponentOfOneKey = NULL;
    if( !CryptImportKey(CryptoProv, PrivateKeyWithExponentOfOne, sizeof(PrivateKeyWithExponentOfOne), 0, 0, &ExponentOfOneKey))
        return false;

    // Insert known key into RC4 key template
    for( int i=0; i<keylen; i++)
        SimpleBlobRC4KeyTemplate[0xc+i] = key[keylen-i-1];
    // Add terminating zero value
    SimpleBlobRC4KeyTemplate[0xc+keylen] = 0;
    // Add non-zero padding
    for( i=keylen+1; i<62; i++)
        SimpleBlobRC4KeyTemplate[0xc+i] = 0x01;

    // Import known key from RC4 template
    HCRYPTKEY RC4Key = NULL;
    DWORD cbBlob = sizeof(SimpleBlobRC4KeyTemplate);
    if (!CryptImportKey(CryptoProv, SimpleBlobRC4KeyTemplate, cbBlob, ExponentOfOneKey, 0, &RC4Key))
        return false;

    // Encrypt the data
    if( !CryptEncrypt( RC4Key, 0, TRUE, 0, Data, pDataLen, *pDataLen))
        return false;

    // Tidy up
    CryptDestroyKey(RC4Key);
    CryptDestroyKey(ExponentOfOneKey);
    CryptReleaseContext(CryptoProv,0);

    return true;
}

///////////////////////////////////////////////////////////////////////////////
//    Main code to exercise EncryptData function

int main( int argc, char* argv[])
{
    printf("RC4Key: Example showing how to set an RC4 key in CryptoAPI.\r\n");
    printf("Chris Cant, PHD Computer Consultants Ltd, http://www.phdcc.com/cryptorc4.htm\r\n\r\n");

    BYTE key[16] = "PHD Computers. ";    // 15 chars + null
    char TestMessage[] = "RC4Key test message encrypted and decrypted OK.\r\n";

    DWORD TestMessageLen = strlen(TestMessage)+1;
    if( !EncryptData( key, 16, (BYTE*)TestMessage, &TestMessageLen))
    {
        printf("RC4Key: EncryptData failed.\r\n");
        return 1;
    }
    if( !EncryptData( key, 16, (BYTE*)TestMessage, &TestMessageLen))
    {
        printf("RC4Key: EncryptData failed.\r\n");
        return 2;
    }
    printf(TestMessage);

    return 0;
}

///////////////////////////////////////////////////////////////////////////////


Chris Cant
Article Copyright � 2001,2003 PHD Computer Consultants Ltd.

PHD Home page