0

I'm trying to make implementation of python AES data decrypting function using c++ openssl. Is it possible? If so, what I'm doing wrong?

python code:

def decrypt(data, key):
    # data will be in hexadecimal representation of binary
    try:
        print(data)
#b'v10|\x80K\x11$\xbf\x8b\xc7\x8bOz\xafc\[some_data]de\x172\xdf\xe8k]\r\xcfm\xf9\x95\xdac'
# length is 121
        iv = data[3:15]
        data= data[15:]
        aes = AES.new(encryption_key, AES.MODE_GCM, iv)
        return aes.decrypt(data)[:-16].decode()
    except:
        print('err')

How c++ code should work in my opinion:

  1. First get key in raw format (main()): key: eRx×LŘé7R¬[some_key]­ReÜüś˘o,
  2. Get data for decrypt() (main()): v10|ÇK$┐őăő[data]»c╚Á╔ßЧ│Ů2▀Ŕk]
  3. Convert it to hexadecimal representation of binary to perform substr(), (other way substr() won't work the same way as in python code). This will be the converted data: v10|\x80K\x11$\xbf\x8b\xc7\x8bOz\xafc\x04[some_data]\xb5\xc9\xe1\xd1\xf5\xb3\xde\x172\xdf\xe8k]\r\xcfm\xf9\x95\xdac
  4. Then perform substr() to extract iv and e_data from the string.
  5. Then convert it back, so that we get raw binary data, which is necessary for final decryption
  6. Perform decryption

I tried the code below in various modifications (including completelyt not using function with converting), but it keeps returning "ERR: FINAL". Why?

c++ code:

#include <iostream>
#include <fstream>
#include <string>
#include <windows.h>
#include <wincrypt.h>
#include <openssl/evp.h>
#include <sstream>
#include <iomanip>
#include <regex>

std::string convert(const std::string& input) {
    std::ostringstream oss;

    for (char ch : input) {
        if (ch >= 32 && ch <= 126) {
            oss << ch;
        } else if (ch == '\r') {
            oss << "\\r";
        } else {
            oss << "\\x" << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(static_cast<unsigned char>(ch));
        }
    }

    return oss.str();
}

std::string frm(const std::string& pybyte) {
    std::string result;
    for (size_t i = 0; i < pybyte.size(); ++i) {
        if (pybyte[i] == '\\' && i + 1 < pybyte.size() && pybyte[i + 1] == 'x') {
            std::string hexValue = pybyte.substr(i + 2, 2);
            char ch = static_cast<char>(std::stoi(hexValue, nullptr, 16));
            result += ch;
            i += 3;
        } else {
            result += pybyte[i];
        }
    }
    return result;
}

std::string rev(const std::string& pybyte) {
    std::ostringstream oss;
    std::string formed = frm(pybyte);

    for (char ch : formed) {
        oss << ch;
    }

    return oss.str();
}

std::string decrypt(const std::string& data, const std::string& key) {
    try {
        std::string cvdat = convert(data);

        std::cout << "encrypted: " << data << std::endl;
        std::cout << "encrypted_conv: " << cvdat << std::endl;
        std::cout << "encrypted_reconv: " << rev(cvdat) << std::endl;

        std::string iv = cvdat.substr(3, 33);
        std::string e_data = cvdat.substr(36);
        std::cout << "iv: " << iv << std::endl;
        std::cout << "edata: " << e_data << std::endl;

        iv = rev(iv);
        e_data = rev(e_data);

        EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
        if (!ctx) {
            std::cout << "ERR: CTX";
            return "";
        }

        if (EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
            std::cout << "ERR: INIT SET";
            EVP_CIPHER_CTX_free(ctx);
            return "";
        }

        if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) {
            std::cout << "ERR: IVLEN";
            EVP_CIPHER_CTX_free(ctx);
            return "";
        }

        if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast<const unsigned char*>(key.c_str()), reinterpret_cast<const unsigned char*>(iv.c_str())) != 1) {
            std::cout << "ERR: KEY, IV" << std::endl;
            EVP_CIPHER_CTX_free(ctx);
            return "";
        }

        std::string decryptedtext(e_data.size(), '\0');
        int decryptedtext_len = 0;

        if (EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char*>(&decryptedtext[0]), &decryptedtext_len, reinterpret_cast<const unsigned char*>(e_data.c_str()), e_data.size()) != 1) {
            std::cout << "ERR: UPDATE";
            EVP_CIPHER_CTX_free(ctx);
            return "";
        }

        int final_len = 0;

        if (EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(&decryptedtext[decryptedtext_len]), &final_len) != 1) {
            std::cout << "ERR: FINAL";
            EVP_CIPHER_CTX_free(ctx);
            return "";
        }

        EVP_CIPHER_CTX_free(ctx);

        decryptedtext_len += final_len;

        decryptedtext.resize(decryptedtext_len);

        return decryptedtext;
    } catch (...) {
        std::cout << "ERR";
        return "";
    }
}

int main() {
    std::string data = "v10|ÇK$┐őă[data]┌6ńW╚Á╔ßЧ│Ů2▀Ŕk]";
    std::string key = "eRx×LŘé7R¬[some_key]­ReÜüś˘o,";
    res = decrypt(data, key);
    std::cout << res;
    
    return 0;
}
Ключ
  • 11
  • 5
  • 2
    "Is it possible?" - Of course it is possible. If it can be done in Python it can be done in C++ (given sufficient skill). – Jesper Juhl Jul 30 '23 at 18:27
  • 1
    Don't ever stringify your data. You can use pointer arithmetic or array operations to indicate where to start encrypting / decrypting. Pointer arithmetic makes most sense **if** you decide to work with a C library such as OpenSSL. Furthermore, your key seems larger than 16 bytes, so you need to use AES192 or 256. – Maarten Bodewes Jul 30 '23 at 18:52
  • 1
    *but it keeps returning "ERR: FINAL". Why?* -- [What is a debugger?](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems). -- *in my opinion* -- There is no need to guess -- use a debugger to see where the code goes against what you expect. – PaulMcKenzie Jul 30 '23 at 19:10
  • 1
    Uh, to be clear, I don't mean: "don't use `std::string`, I do mean: "don't use hexadecimals, or text oriented functionality". In C / C++ the difference between a byte and a character is not so sharply drawn. Oh, and in Python that last index is **exclusive**. GCM takes, by default, a 12 byte IV and you're using 30 characters. That cannot be right. – Maarten Bodewes Jul 30 '23 at 20:54
  • 1
    I don't exactly know what goes into somebody's head when they name a utility function `convert`. I had a nice little talk when a new project manager wanted me to create a "communication server". I indicated that all servers communicated, and that was kind of the end of that talk. I do hope you get the point. Then again, with the C language, I guess that's kind of the mindset, with functions like `itoa`. Integer to ASCII.. yeah, OK, but **how**?. – Maarten Bodewes Jul 30 '23 at 20:58
  • 1
    You should not copy the keys using UTF-8, you might lose data that way. When specifying a key or IV in hexadecimals then you **should** use hexadecimal encoding instead. Python gets around this by escaping some **unprintable** characters, but in C++ this will fail. – Maarten Bodewes Jul 30 '23 at 21:05
  • Your v10 prefix implies that you are dealing with encrypted Chrome cookies/passwords: For this 1. AES-**256** is used, 2. the encrypted data is equal to the *concatenation* of the v10 prefix (3 bytes), the nonce (12 bytes), the ciphertext and the tag (16 bytes). – Topaco Jul 31 '23 at 15:50
  • Your C++ decryption also fails because you do not take the GCM tag into account (unlike PyCryptodome, which tolerates decryption without authentication using `decrypt()`, OpenSSL performs *mandatory* authentication, at least when calling `EVP_DecryptFinal_ex()`). – Topaco Jul 31 '23 at 16:01
  • For data transfer from Python to C++ you should use a reliable binary-to-text encoding, e.g. hex encoding (see [here](https://stackoverflow.com/a/3790661/9014097) for the decoding on the C++ side) or Base64. This is less cumbersome (`convert()`, `frm()` and `rev()` are then obsolete) and reliable (your way is probably broken or at least unreliable because you apply a charset encoding to convert the data to a string). – Topaco Jul 31 '23 at 17:22
  • @Topaco thank you for your responses. Unfortunately, that didn't work either. I'll perform decryption in other way as I'm not fluent in c++ yet – Ключ Aug 01 '23 at 13:14
  • @MaartenBodewes-onstrike what's the big deal? I'll just rename the function to raw_to_py_byte() or sth. Anyway, thank you I'll try your method – Ключ Aug 01 '23 at 13:19
  • 1
    It works, I have adapted your C++ code accordingly and tested it on my machine (the adaptation concerns a correct decoding of ciphertext and key and the consideration of the GCM tag). – Topaco Aug 01 '23 at 13:31
  • @Topaco is the tag at the end of the string? like this: `std::string tag = data.substr(-16)`? – Ключ Aug 02 '23 at 15:41
  • Yes, the tag is at the end (the last 16 bytes). – Topaco Aug 02 '23 at 16:58
  • @Topaco it worked! I spent over 3 days trying to figure it out. Thank you so much – Ключ Aug 04 '23 at 14:32

0 Answers0