0

I have a python endpoint that encrypts string using AES cbc mode and returns it to the client software written in c++ (in a hex space separated format ) The link for the c++ repo


std::vector<unsigned char> cipher_as_chars(std::string cipher) 
{
    std::istringstream strm{cipher};
    strm >> std::hex;
    std::vector<unsigned char> res;
    res.reserve(cipher.size() / 3 + 1);
    int h;
    while (strm >> h) {
        res.push_back(static_cast<unsigned char>(h));
    }
    return res;
}
namespace client{
    std::string decrypt_cipher(std::string cipher, std::string usr_key)
    {
        std::string original_text  = "";
        const std::vector<unsigned char> key = key_from_string(usr_key);      // 16-char = 128-bit

        const unsigned char iv[16] = {
            0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x49, 0x56, 0x34, 0x35, 0x36
        };
       
        std::vector<unsigned char> encrypted = cipher_as_chars(cipher);
        unsigned long padded_size = 0;
        std::vector<unsigned char> decrypted(encrypted.size());

        plusaes::decrypt_cbc(&encrypted[0], encrypted.size(), &key[0], key.size(), &iv, &decrypted[0], decrypted.size(), &padded_size);
        


        for (int i =0 ; i < decrypted.size(); i++)
        {
            //cout << decrypted[i] << endl;
            std::stringstream stream;
            stream << decrypted[i];
            original_text = original_text + stream.str();
        }

        return original_text;
    }
}

def encrypt_string(key,text):
    result = ''
    while len(text)% 16 != 0 :
        text = text+" "
    string_as_bytes = text.encode('utf8')
    
    obj = AES.new(key.encode("utf8"), AES.MODE_CBC, 'This is an IV456'.encode("utf8"))
    cipher_text = obj.encrypt(string_as_bytes)
    
    for item in bytearray(cipher_text):
        result += f"{hex(item).replace('0x','')} "
    return result

@api.route('/test')
def test_route():
    return encrypt_string("Encryptionkey123", "Happy new year people")


The server has an encryption and decryption function same with the client software if I encrypt a string using the c++ code and decrypt it using decryption function written in c++ it works fine and I get the same string, but when the client reads the response of /test and encrypts it and the c++ client tries to decrypt it outputs the string missing n letters in the end

The original text in python server Happy new year people

The output in the c++ client Happy new year p

khalid
  • 67
  • 1
  • 8
  • 1
    What have you done to debug the problem? Have you printed out all input / output as hexadecimals? You are talking about getting part of a string, did you look at the ciphertext size before sending & after receiving? Which libraries are you using? Please provide an [mcve]. – Maarten Bodewes Jan 02 '22 at 16:33
  • @MaartenBodewes for the c++ side i provided the link for the AES repo for python I am using `pycrypto` as for decrypted size on the c++ side it is `16` letters long less than the original by 5 – khalid Jan 02 '22 at 16:39
  • Alright, I stand corrected about the C++ library - although why you would use such a library is beyond me (I mean, I'm not a C++ developer, but a push request for changing an `int` to `t_size` which isn't understood. Really? Why are you ignoring the `Error` returned? Don't you think that this is the very basic debugging and validation that you can do? – Maarten Bodewes Jan 02 '22 at 16:46
  • @MaartenBodewes I am using that repo because it is a single header library that supports AES encryption as for the `int` to `t_size` I fixed that already and for the debugging side I am no expert that is why I am asking here – khalid Jan 02 '22 at 16:55
  • Good, so I presume you are typing now and trying to find out the return value, comparing **ciphertext** size on both sides and printing out input / output in hexadecimals? – Maarten Bodewes Jan 02 '22 at 17:00
  • Yes I added a line to the loop in the `decrypt_cipher` to output each `i` item in the `decrypted`, `encrypted` vectors but it does not seem to help and the total length of `original_text` is 32 – khalid Jan 02 '22 at 17:02
  • What are you putting in `encrypted`? The method `cipher_as_chars` doesn't seem to perform hexadecimal decoding... Does it work correctly? – Maarten Bodewes Jan 02 '22 at 17:12
  • @MaartenBodewes `cipher_as_chars` is supposed to convert a string from `41 78 f2 23 c5 ba 6d c5 cf ee 46 84 3b b5 f d5` to a vector of `unsigned char`s – khalid Jan 02 '22 at 17:16
  • The main issue that I see is that you ignore `padded_size` while obviously you should use `decrypted.size() - padded_size` in the `for` loop. That however still doesn't explain why you would get **less** information instead of more, but who knows what is in the remaining characters, maybe is it the backspace control code or something similar. – Maarten Bodewes Jan 02 '22 at 17:21
  • @MaartenBodewes I used the bare example from the github repo and printed the `raw_data.size()`,`encrypted.size()`,`decrypted.size()` and got this output ```Padded size : 32 Size of raw data21 Decrypted size : 32``` I am using `happy new year people` as the text to be encrypted – khalid Jan 02 '22 at 17:33

2 Answers2

3

Look at this example pycrypto does support pkcs#7 padding your take on padding is poor, just use the built-in padding function in that module

Example taken from the link

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Util.Padding import unpad

key=b'1234567890123456'
cipher=AES.new(key,AES.MODE_CBC)

text=b'secret text'
padtext=pad(text,16,style='pkcs7')
cipherText=cipher.encrypt(padtext)
print(padtext)
print(cipherText)

plaintext=cipher.decrypt(cipherText)  #can't use same object to decrypt
print(plaintext)

you might want to add some code to turn the result into a hex separated string

Weed Cookie
  • 579
  • 1
  • 3
  • 11
  • 1
    Ah nice, note that the unpadding also takes the block size, so the additional check can be performed. – Maarten Bodewes Jan 02 '22 at 22:44
  • @MaartenBodewes I am no professional coder but a simple google search solves the problem – khalid Jan 02 '22 at 22:45
  • 1
    Beware that a simple Google search also shows countless insecure cryptography examples that "just work". It's generally fine for API information of highly used libs of course. There is no difference between professional and amateur coders, it is just how you take on the challenges. In this case I forgot that this library added padding methods, so good that Weed Cookie posted the answer. – Maarten Bodewes Jan 02 '22 at 22:49
  • @MaartenBodewes couldn't agree anymore – Weed Cookie Jan 02 '22 at 22:50
1

What's wrong is that the C++ library uses PKCS#7 padding and the python code uses spaces. The horrid C++ library doesn't check if the padding is within the block size nor the padding, so it directly unpads given the value of the final character found - for space this is 0x20. So this will unpad 32 bytes instead of any normal padding size (I wonder what it does when the padding is larger than the remaining plaintext size - possibly a buffer underflow?).

You have to implement PKCS#7 padding in the python. This is adding 1 byte valued 0x01 to 16 bytes valued 0x10. You can see an implementation here. Note that the unpadding shown there is as vulnerable as in the C++ library: instead you 1 check if the last padding byte is in the range 1..<blocksize=16> and then check if all the other padding bytes are correct as well, creating an exception or error code if not.

Also note CBC padding oracles, the use of a static IV etc. Use TLS for transport security.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263