|
Mian text: 19 June 2001 Last modified: 3 June 2003. |
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.
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/.
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.
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.
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; } /////////////////////////////////////////////////////////////////////////////// |