0

I'm stuck with the problem of decrypting AES-CBC ecrypted string. I have JS code which decrypt that string but I need do that in C++. Key is string of SHA512 hash, and message is string of Base64. The JS code for decrypt:

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());

My C++ code doesn't work

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;
}

Maybe I should use OpenSSL?

DreamCoder
  • 37
  • 6
  • CryptoJS uses an OpenSSL specific Password-Based Key Derivation Function if you supply a passphrase: "If you use a passphrase, then it will generate a 256-bit key." For this kind of shizzle and stupid backwards compatibility (OpenSSL command line is not that secure) I suggest you use the OpenSSL library, see e.g. [here](https://stackoverflow.com/q/9488919/589259). Yes, JSCrypto is stupid that it named the function just `encrypt` with weird overloads. – Maarten Bodewes Jan 23 '23 at 23:02
  • It's late, so I used stupid a few too many times. But I had to answer the question too many times as well. – Maarten Bodewes Jan 23 '23 at 23:22

1 Answers1

1

The ciphertext generated by the posted CryptoJS code cannot be decrypted by any AES compliant library. This is due to the line

CryptoJS.algo.AES.keySize = 32

which defines a keysize of 32 words = 32 * 4 = 128 bytes for key derivation. This is not a valid AES keysize and the derived number of rounds is not defined for AES at all (38 rounds for 128 bytes, see here; AES defines only 10, 12 and 14 rounds depending on the key size). The ciphertext is therefore not AES compliant. It can be decrypted with CryptoJS, but not by any AES compliant library, see also this CryptoJS issue #293. For the generated ciphertext to be AES compatible, one of the allowed AES key sizes must be used, e.g. a keysize of 8 words = 32 bytes:

CryptoJS.algo.AES.keySize = 8

Furthermore, note that line

CryptoJS.algo.EvpKDF.cfg.iterations = 10000

leads to incomapatability with the OpenSSL CLI, which by default uses an iteration count of 1 in key derivation (which is one of the reasons why this key derivation is weak, see here).

By the way, the line

CryptoJS.algo.EvpKDF.cfg.keySize = 32

is completely ignored by the processing and can also be omitted.


If a valid AES key size is used, e.g. 8 words = 32 bytes:

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());

the ciphertext can be decrypted programmatically. As already mentioned in the comments, CryptoJS uses the OpenSSL propritary key derivation function EVP_BytesToKey() if the key material is passed as string. This generates an 8 bytes salt during encryption and uses the salt and password to derive key and IV. These are used to encrypt in CBC mode with PKCS#7 padding by default. OpenSSL formats the result of the encryption as a concatenation of the ASCII encoding of Salted__, followed by the 8 bytes salt and finally by the actual ciphertext, usually Base64 encoded.

For decryption, the salt and ciphertext must be separated. Then, based on salt and password, key and IV are to be determined, with which finally the ciphertext is decrypted.
Thus, for decryption an implementation for EVP_BytesToKey() is needed. Such an implementation can be found for Crypto++ here in the Crypto++ docs, and a code with which the ciphertext of the CryptoJS code can be decrypted (after fixing the keysize issue) is e.g.:

#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;
}
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Thank you for your reply! I've compiled the code and the program show me dialog "abort() has been called":( – DreamCoder Jan 25 '23 at 00:49
  • 1
    @DreamCoder - The code works, at least on my machine. Maybe there is a bug that doesn't cause an exception in my environment, but does in yours. Or maybe you just need to adapt it for your environment. So, try to find out what is causing the problem in your environment, e.g. debugging, commenting out code areas etc. until you have found the exact source code location that triggers the exception. – Topaco Jan 25 '23 at 07:27
  • Okay I got it. Am I right if I need to decrypt ciphertext generated by CryptoJS code I should copy full realization of the CryptoJS AES code to C++ ? Is this possible task? – DreamCoder Jan 25 '23 at 14:13
  • @DreamCoder - You didn't post a CryptoJS code for encryption, so that can only be answered in a blanket way: You need to implement a decryption in the C++ code that is the exact matching counterpart of the encryption in the CryptoJS code. So if the CryptoJS code complies with standards and doesn't do crazy things (like applying a 128 bytes key), it's generally possible. – Topaco Jan 25 '23 at 15:12