I am working on a product that needs to be able to consume files created with an older product. Some of these files contain content encrypted with RC4 encryption using MS CryptoAPI. I have thus far been unable to successfully decrypt the content using other crypto libraries. After some experimentation, it appears that CryptoAPI's RC4 output is "correct" (i.e. agrees with other libraries) when the key is ASCII, but is "wrong" (differs from other libraries that all agree) when the key is not ASCII (e.g. a hash of a password).
Since all the content I'm interested in consuming was encrypted with a key that was derived from a password via hashing, I'm a bit stuck at the moment. I've written up a small test to show the issue that includes 3 test cases as you can see below in the code. Botan (C++) and CryptoJS (JS) always agree on output. MS CryptoAPI, however, only agrees for the ASCII keys.
Before I go to MS with this issue, is anyone aware of something I'm misunderstanding or doing wrong that could cause this issue?
Also, I apologize for my atrocious javascript.
#pragma pack (push, 1)
struct PlainTextKeyBlob
{
BLOBHEADER _hdr;
DWORD _cbKey;
BYTE _key[1];
};
#pragma pack (pop)
void TestBotanAndMSCryptoRC4()
{
struct TestItem
{
std::string key;
std::string plainText;
};
TestItem TestItems[] = {
{ "Secret", "Attack at dawn" }, // Example taken from Wikipedia RC4 page to verify output.
{ "!\\\"#$%&'()*+", "Encrypt me" } , // Key with various ASCII symbols.
{ "\xF4\xE7\xA8\x74\x0D", "Encrypt me" } // Key is first 5 bytes of SHA1 hash of "Secret".
};
DWORD NumTestItems = _countof(TestItems);
for( DWORD i = 0; i < NumTestItems; i++ )
{
// Botan Encryption
Botan::SymmetricKey symmKey((BYTE*)TestItems[i].key.c_str(), TestItems[i].key.size());
Botan::Pipe pipe(Botan::get_cipher("ARC4", symmKey, Botan::ENCRYPTION));
pipe.process_msg(TestItems[i].plainText);
SecureByteVector& encryptedBuff = pipe.read_all();
// MS Crypto API Encryption
AutoCryptProv CryptProv;
if( !CryptAcquireContext( CryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
ASSERT(false);
return;
}
DWORD blobKeySize = TestItems[i].key.size();
DWORD blobSize = sizeof(PlainTextKeyBlob) + blobKeySize - 1;
CByteArray keyBlob;
keyBlob.SetSize(blobSize);
PlainTextKeyBlob *pKeyBlob = reinterpret_cast<PlainTextKeyBlob*>(keyBlob.GetData());
pKeyBlob->_hdr.bType = PLAINTEXTKEYBLOB;
pKeyBlob->_hdr.bVersion = CUR_BLOB_VERSION;
pKeyBlob->_hdr.reserved = 0;
pKeyBlob->_hdr.aiKeyAlg = CALG_RC4;
pKeyBlob->_cbKey = TestItems[i].key.size();
memcpy_s(pKeyBlob->_key, blobKeySize, TestItems[i].key.c_str(), TestItems[i].key.size());
AutoCryptKey CryptKey;
if( !CryptImportKey(CryptProv, reinterpret_cast<BYTE*>(pKeyBlob), blobSize, NULL, 0, CryptKey) )
{
ASSERT(false);
return;
}
CByteArray dataBytes;
dataBytes.SetSize(TestItems[i].plainText.size());
memcpy_s(dataBytes.GetData(), dataBytes.GetSize(), TestItems[i].plainText.c_str(), TestItems[i].plainText.size());
DWORD buffSize = dataBytes.GetSize();
if( !CryptEncrypt(CryptKey, 0, TRUE, 0, dataBytes.GetData(), &buffSize, dataBytes.GetSize()) )
{
ASSERT(false);
return;
}
ASSERT(encryptedBuff.size() == dataBytes.GetSize());
ASSERT( 0 == memcmp(encryptedBuff.begin(), dataBytes.GetData(), dataBytes.GetSize()) );
}
}
<html>
<body>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/rc4.js"></script>
<div id="output" />
<script>
var key1 = CryptoJS.enc.Hex.parse('536563726574'); // Secret
var message1 = "Attack at dawn";
var encrypted1 = CryptoJS.RC4.encrypt(message1, key1);
var key2 = CryptoJS.enc.Hex.parse('215c22232425262728292a2b'); // !\"#$%&'()*+
var message2 = "Encrypt me";
var encrypted2 = CryptoJS.RC4.encrypt(message2, key2);
var key3 = CryptoJS.enc.Hex.parse('f4e7a8740d'); // First 5 bytes of hash of "Secret"
var message3 = "Encrypt me";
var encrypted3 = CryptoJS.RC4.encrypt(message3, key3);
var elem = document.getElementById("output");
elem.innerHTML = "Key1: " + encrypted1.key + "<br> ciphertext1: " + encrypted1.ciphertext + "<br><br>" +
"Key2: " + encrypted2.key + "<br> ciphertext2: " + encrypted2.ciphertext + "<br><br>" +
"Key3: " + encrypted3.key + "<br> ciphertext3: " + encrypted3.ciphertext;
</script>
</body>
</html>