Я застрял с проблемой расшифровки зашифрованной строки AES-CBC. У меня есть код JS, который расшифровывает эту строку, но мне нужно сделать это на С++. Ключ — это строка хэша SHA512, а сообщение — строка Base64. JS-код для расшифровки:
CryptoJS.algo.AES.keySize = 32,
CryptoJS.algo.EvpKDF.cfg.iterations = 10000,
CryptoJS.algo.EvpKDF.cfg.keySize = 32;
var r = CryptoJS.AES.decrypt(message, key.toString());
Мой код C++ не работает
std::string generateIV(std::string key)
{
std::string iv(CryptoPP::AES::BLOCKSIZE, 0);
CryptoPP::SHA1().CalculateDigest((byte*)iv.data(), (byte*)key.data(), key.size());
return iv;
}
std::string decrypt(std::string &message, std::string &key) {
std::string decrypted;
std::string iv(generateIV(key));
// Create the AES decryption object
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption aesDecryption;
aesDecryption.SetKeyWithIV((byte*)key.data(), key.size(), (byte*)iv.data(), iv.size());
// Decrypt the message
CryptoPP::StringSource ss(message, true,
new CryptoPP::StreamTransformationFilter(aesDecryption,
new CryptoPP::StringSink(decrypted)
)
);
return decrypted;
}
Может быть, мне следует использовать OpenSSL?
Уже поздно, поэтому я использовал глупость слишком много раз. Но мне тоже приходилось слишком много раз отвечать на вопрос.
Зашифрованный текст, сгенерированный опубликованным кодом CryptoJS, не может быть расшифрован какой-либо библиотекой, совместимой с AES. Это из-за линии
CryptoJS.algo.AES.keySize = 32
который определяет размер ключа 32 слова = 32 * 4 = 128 байтов для получения ключа. Это недопустимый размер ключа AES, и полученное количество раундов вообще не определено для AES (38 раундов для 128 байт, см. здесь ; AES определяет только 10, 12 и 14 раундов в зависимости от размера ключа). Таким образом, зашифрованный текст не соответствует стандарту AES. Его можно расшифровать с помощью CryptoJS, но не с помощью какой-либо библиотеки, совместимой с AES, см. также этот выпуск CryptoJS #293. Чтобы сгенерированный зашифрованный текст был совместим с AES, необходимо использовать один из разрешенных размеров ключа AES, например. размер ключа 8 слов = 32 байта:
CryptoJS.algo.AES.keySize = 8
Кроме того, обратите внимание на эту строку
CryptoJS.algo.EvpKDF.cfg.iterations = 10000
приводит к несовместимости с интерфейсом командной строки OpenSSL, который по умолчанию использует число итераций, равное 1, при получении ключа (что является одной из причин слабости этого вывода ключа, см. здесь).
Кстати, линия
CryptoJS.algo.EvpKDF.cfg.keySize = 32
полностью игнорируется обработкой и также может быть опущен.
Если используется действительный размер ключа AES, например. 8 слов = 32 байта:
CryptoJS.algo.AES.keySize = 8, // 8 words = 32 bytes
CryptoJS.algo.EvpKDF.cfg.iterations = 10000,
CryptoJS.algo.EvpKDF.cfg.keySize = 32;
var r = CryptoJS.AES.decrypt(message, key.toString());
зашифрованный текст может быть расшифрован программно. Как уже упоминалось в комментариях, CryptoJS использует собственную функцию получения ключа OpenSSL EVP_BytesToKey()
, если материал ключа передается в виде строки. Это генерирует 8-байтовую соль во время шифрования и использует соль и пароль для получения ключа и IV. Они используются для шифрования в режиме CBC с заполнением PKCS#7 по умолчанию. OpenSSL форматирует результат шифрования как конкатенацию кодировки ASCII Salted__, за которой следует 8-байтовая соль и, наконец, фактический зашифрованный текст, обычно закодированный Base64.
Для расшифровки соль и зашифрованный текст должны быть разделены. Затем на основе соли и пароля определяются ключ и IV, с помощью которых окончательно расшифровывается зашифрованный текст.
Таким образом, для расшифровки необходима реализация для EVP_BytesToKey()
. Такую реализацию можно найти для Crypto++ здесь в документации Crypto++, а код, с помощью которого можно расшифровать зашифрованный текст кода CryptoJS (после исправления проблемы с размером ключа), например:
#include "aes.h"
#include "modes.h"
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include "md5.h"
#include "base64.h"
#include "secblock.h"
static int OPENSSL_PKCS5_SALT_LEN = 8;
int OPENSSL_EVP_BytesToKey(CryptoPP::HashTransformation& hash, const unsigned char* salt, const unsigned char* data, int dlen, unsigned int count, unsigned char* key, unsigned int ksize, unsigned char* iv, unsigned int vsize);
int main(int, char**) {
// Pass data and parameter
std::string passphrase = "my passphrase";
std::string encryptedB64 = "U2FsdGVkX18AuE7abdK11z8Cgn3Nc+2cELB1sWIPhAJXBZGhnw45P4l58o33IEiJ8fV4oEid2L8wKXpAntPrAQ= = "; // CryptoJS ciphertext for a 32 bytes keysize
std::string encrypted;
int iterationCount = 10000;
int keySize = 32;
// Base64 decode
CryptoPP::StringSource ssB64decodeCt(encryptedB64, true,
new CryptoPP::Base64Decoder(
new CryptoPP::StringSink(encrypted)
)
);
// Separate
std::string salt(encrypted.substr(8, 8));
std::string ciphertext(encrypted.substr(16));
// Derive key
CryptoPP::SecByteBlock key(keySize), iv(16);
CryptoPP::Weak::MD5 md5;
OPENSSL_EVP_BytesToKey(md5, (const unsigned char*)salt.data(), (const unsigned char*)passphrase.data(), passphrase.size(), iterationCount, key.data(), key.size(), iv.data(), iv.size());
// Decryption
std::string decryptedText;
CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption decryption(key.data(), key.size(), iv.data());
CryptoPP::StringSource ssDecryptCt(
ciphertext,
true,
new CryptoPP::StreamTransformationFilter(
decryption,
new CryptoPP::StringSink(decryptedText),
CryptoPP::BlockPaddingSchemeDef::BlockPaddingScheme::PKCS_PADDING
)
);
// Output
std::cout << decryptedText << std::endl; // The quick brown fox jumps over the lazy dog
return 0;
}
// from: https://www.cryptopp.com/wiki/OPENSSL_EVP_BytesToKey
int OPENSSL_EVP_BytesToKey(CryptoPP::HashTransformation& hash, const unsigned char* salt, const unsigned char* data, int dlen, unsigned int count, unsigned char* key, unsigned int ksize, unsigned char* iv, unsigned int vsize)
{
if (data == NULL) return (0);
unsigned int nkey = ksize;
unsigned int niv = vsize;
unsigned int nhash = hash.DigestSize();
CryptoPP::SecByteBlock digest(nhash);
unsigned int addmd = 0, i;
for (;;)
{
hash.Restart();
if (addmd++)
hash.Update(digest.data(), digest.size());
hash.Update(data, dlen);
if (salt != NULL)
hash.Update(salt, OPENSSL_PKCS5_SALT_LEN);
hash.TruncatedFinal(digest.data(), digest.size());
for (i = 1; i < count; i++)
{
hash.Restart();
hash.Update(digest.data(), digest.size());
hash.TruncatedFinal(digest.data(), digest.size());
}
i = 0;
if (nkey)
{
for (;;)
{
if (nkey == 0) break;
if (i == nhash) break;
if (key != NULL)
*(key++) = digest[i];
nkey--;
i++;
}
}
if (niv && (i != nhash))
{
for (;;)
{
if (niv == 0) break;
if (i == nhash) break;
if (iv != NULL)
*(iv++) = digest[i];
niv--;
i++;
}
}
if ((nkey == 0) && (niv == 0)) break;
}
return ksize;
}
Спасибо за ваш ответ! Я скомпилировал код, и программа показывает мне диалоговое окно «Вызов abort()» :(
@DreamCoder - код работает, по крайней мере, на моей машине. Возможно, есть ошибка, которая не вызывает исключения в моей среде, но делает это в вашей. Или, может быть, вам просто нужно адаптировать его для вашей среды. Итак, попытайтесь выяснить, что вызывает проблему в вашей среде, например. отладка, комментирование областей кода и т. д., пока вы не найдете точное местоположение исходного кода, которое вызывает исключение.
Хорошо, я понял. Я прав, если мне нужно расшифровать зашифрованный текст, сгенерированный кодом CryptoJS, я должен скопировать полную реализацию кода CryptoJS AES на C++? Возможна ли эта задача?
@DreamCoder - вы не опубликовали код CryptoJS для шифрования, поэтому на него можно ответить только в общих чертах: вам необходимо реализовать дешифрование в коде C++, которое является точным аналогом шифрования в коде CryptoJS. Так что, если код CryptoJS соответствует стандартам и не делает сумасшедших вещей (например, не применяет 128-байтовый ключ), это, как правило, возможно.
CryptoJS использует специфичную для OpenSSL функцию получения ключа на основе пароля, если вы укажете парольную фразу: «Если вы используете парольную фразу, она сгенерирует 256-битный ключ». Для такого рода шизла и глупой обратной совместимости (командная строка OpenSSL не так безопасна) я предлагаю вам использовать библиотеку OpenSSL, см., например. здесь. Да, АОКрипто дурак, что назвал функцию просто
encrypt
со странными перегрузками.