我正在开发一种产品,它需要能够使用旧产品创建的文件。其中一些文件包含使用MS CryptoAPI通过RC4加密加密的内容。到目前为止,我还无法使用其他加密库成功解密内容。经过一些实验,当密钥是ASCII时,似乎CryptoAPI的RC4输出是“正确的”(即与其他库一致),但是当密钥不是ASCII时,它是“错误的”(与所有同意的其他库不同)(例如a密码哈希)。
由于我感兴趣的所有内容都是使用通过散列从密码派生的密钥加密的,所以我现在有点卡住了。我已经编写了一个小测试来显示包含3个测试用例的问题,如下面的代码所示。 Botan(C ++)和CryptoJS(JS)总是同意输出。但是,MS CryptoAPI仅同意ASCII密钥。
在我遇到这个问题之前,有没有人意识到我可能会误解或做错的事情可能导致这个问题?
另外,我为我残暴的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>
答案 0 :(得分:0)
我只想跟进我找到的答案,以防将来对其他人有用。
首先,答案是RTFM!事实证明,默认情况下,CryptDeriveKey为由全0组成的40位对称密钥添加“salt”。当我查看我们的旧CryptoAPI代码并看到我们没有向其传递任何标志时,我认为这意味着没有什么特别的事情发生,我没有阅读MSDN上所有可能标志的详细信息。此外,由于MS认为密钥的这一部分是盐,因此在导出密钥时不会包括它,因此这也是一个死胡同。
我最终找到了http://msdn.microsoft.com/en-us/library/windows/desktop/aa387695(v=vs.85).aspx的方法,它解释了40位密钥的盐机制,引起我注意的是,为了兼容性,你应该使用CRYPT_NO_SALT标志创建密钥。在我们的例子中,我们已经使用这些密钥加密了内容,因此我们只需修改我们的Botan / CryptoJS代码,将11个字节的0附加到基本40位密钥的末尾。
可以在此处找到各种CryptDeriveKey标志的详细信息:http://msdn.microsoft.com/en-us/library/windows/desktop/aa379916(v=vs.85).aspx