MS CryptoAPI дает неверные результаты RC4?

Я работаю над продуктом, который должен иметь возможность использовать файлы, созданные с помощью более старого продукта. Некоторые из этих файлов содержат содержимое, зашифрованное с помощью шифрования RC4 с использованием MS CryptoAPI. До сих пор мне не удалось успешно расшифровать содержимое с помощью других криптобиблиотек. После некоторых экспериментов оказалось, что выходные данные RC4 CryptoAPI являются «правильными» (т. е. согласуются с другими библиотеками), когда используется ключ ASCII, но «неправильными» (отличается от других библиотек, которые все согласуются), когда ключ не является ASCII (например, хэш пароля).

Поскольку весь интересующий меня контент был зашифрован с помощью ключа, полученного из пароля с помощью хеширования, я немного застрял в данный момент. Я написал небольшой тест, чтобы показать проблему, которая включает в себя 3 тестовых примера, как вы можете видеть ниже в коде. Botan (C++) и CryptoJS (JS) всегда согласуются в выводе. Однако MS CryptoAPI соглашается только на ключи ASCII.

Прежде чем я отправлюсь в MS с этой проблемой, кто-нибудь знает, что я неправильно понимаю или делаю неправильно, что может вызвать эту проблему?

Кроме того, я извиняюсь за мой ужасный 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>

person Derek P. Miller    schedule 07.08.2013    source источник


Ответы (1)


Я просто хотел продолжить ответ, который я нашел, на случай, если это будет полезно кому-то еще в будущем.

Прежде всего, ответ был на RTFM! Как оказалось, по умолчанию CryptDeriveKey добавляет «соль» к 40-битным симметричным ключам, состоящим из одних нулей. Когда я посмотрел на наш старый код CryptoAPI и увидел, что мы не передаем в него никаких флагов, я предположил, что это означает, что ничего особенного не происходит, и я не читал подробности всех возможных флагов в MSDN. Кроме того, поскольку MS считает эту часть ключа солью, она не включается при экспорте ключа, так что это тоже тупик.

В конце концов я нашел свой путь к http://msdn.microsoft.com/en-us/library/windows/desktop/aa387695(v=vs.85).aspx, который объясняет механизм соли для 40-битных ключей, и строка, которая привлекла мое внимание, была что для совместимости следует создавать ключи с флагом CRYPT_NO_SALT. В нашем случае у нас уже есть зашифрованный контент с такими ключами, поэтому мы просто изменили наш код Botan/CryptoJS, добавив 11 байтов нулей в конец базового 40-битного ключа.

Подробную информацию о различных флагах CryptDeriveKey можно найти здесь: http://msdn.microsoft.com/en-us/library/windows/desktop/aa379916(v=vs.85).aspx

person Derek P. Miller    schedule 27.09.2013