简体   繁体   中英

c++ to php translation, decryption function

So, I'm trying to translate a piece of C++ code to php. The C++ is from a external source, and my knowledge of both C++ and decryption is lacking, to say the least.

The source C++ is:

void parser_t::decrypt(buffer_t &replay_data, const unsigned char *key_data) {  
    /*\ 
    |*| Performs an in place decryption of the replay using the given key.
    |*| The decryption is a (broken) variant of CBC decryption and is performed as follows:
    |*| -# Set the variable previous_block (with size of 16 bytes) to 0
    |*| -# Decrypt a block with the given key
    |*| -# XOR the block with the previous (decrypted) block
    |*| -# Go back to step 2, until there are no more blocks.
    \*/
    BF_KEY key = {{0}};
    BF_set_key(&key, 16, key_data);

    const int block_size = 8;

    size_t padding_size = (block_size - (replay_data.size() % block_size));
    if (padding_size != 0) {
        size_t required_size = replay_data.size() + padding_size;
        replay_data.resize(required_size, 0);
    }

    unsigned char previous[block_size] = {0};
    for (auto it = replay_data.begin(); it != replay_data.end(); it += block_size) {
        unsigned char decrypted[block_size] = { 0 };
        BF_ecb_encrypt(reinterpret_cast<unsigned char*>(&(*it)), decrypted, &key, BF_DECRYPT);
        std::transform(previous, previous + block_size, decrypted, decrypted, std::bit_xor<unsigned char>());
        std::copy_n(decrypted, block_size, previous);
        std::copy_n(decrypted, block_size, reinterpret_cast<unsigned char*>(&(*it)));
    }

    if (padding_size != 0) {
        size_t original_size = replay_data.size() - padding_size;
        replay_data.resize(original_size, 0);
    }
}

What I have got so far:

function decrypt($data){ // $data is a encrypted string
    $key = array(0xDE, <.....>, 0xEF); // (16 entries in the array)

    //BF_KEY key = {{0}};             // ?
    //BF_set_key(&key, 16, key_data); // ?

    $block_size = 8;

    $padding_size = ($block_size - (strlen($data) % $block_size));
    if ($padding_size != 0) {
        $required_size = strlen($data) + $padding_size;
        //replay_data.resize(required_size, 0);
        // Seems unnecessary in php? string lengths are pretty dynamic.
    }

    $keyString = '';
    for($i = 0; $i < count($key); $i++){
        $keyString .= chr($key[$i]);
    }
    $output = '';

    for ($i = 0; $i < stlen($data); $i += $block_size) {
        $char = array(0, 0, 0, 0, 0, 0, 0, 0);                     // ?
        $decrypted_piece = mcrypt_decrypt(MCRYPT_BLOWFISH, $keyString, $data, "cbc"); // ??
        // And this is where I completely get lost.
        $output = transform($in, $start, $end, $in2);
    }
}

function transform($in, $start, $end, $in2){
    $out = ''; // Yea, that won't work...
    for($x = $start; $x < $end; $x++){
        $out[$x] = $in[$x] ^ $in2[$x];
    }
    return $output
}

I realize I'm basically asking you guys to do something for me, but I'm really stuck at the contents of that for (auto it... .

Hints / explanations that'd really help me along would be:

  • What does BF_ecb_encrypt do in this case? (In pseudocode or even php?) ( slaps self on fingers. "don't ask for finished products" )
  • Am I on the right track with the translation of the transform ?
  • What is {{0}} , BF_set_key(&key, 16, key_data); ?
  • What is reinterpret_cast<unsigned char*>(&(*it)) ?

I did get a look at these documentation pages, but to no avail:

The full source is available on github .
This specific code comes from src/parser.cpp

The "broken variant of CBC decryption" that the original code is doing can, equivalently, be described as ECB decryption followed by (cumulatively) XORing each plaintext block with the previous one.

For the XOR, you don't need anything fancy: the PHP bitwise XOR operator can operate on strings.

Thus, a simple PHP version of your C++ code could look something like this (warning: untested code):

function decrypt( $ciphertext, $key ) {
    $plaintext = mcrypt_decrypt( MCRYPT_BLOWFISH, $key, $ciphertext, "ecb" );

    $block_size = 8;  // Blowfish block size = 64 bits = 8 bytes
    $blocks = str_split( $plaintext, $block_size );

    $previous = str_repeat( "\0", $block_size );
    foreach ( $blocks as &$block ) {
        $block ^= $previous;
        $previous = $block;
    }

    return implode( $blocks );
}

Note that I haven't implemented any padding for truncated last blocks; there's something very screwy about the padding handling in the original code, and I don't see how it could possibly correctly decrypt messages whose length is not divisible by 8 bytes. (Does it, actually?) Rather than try to guess what the heck is going on and how to translate it to PHP, I just chose to ignore all that stuff and assume that the message length is divisible by the block size.

What does BF_ecb_encrypt do in this case?

BF_ecb_encrypt() is a function for encrypting using blowfish. The PHP equivalent (as previously mentioned by Ilmari Karonen) is $plaintext = mcrypt_decrypt( MCRYPT_BLOWFISH, $key, $ciphertext, "ecb" );

What is reinterpret_cast(&(*it))?

BF_ecb_encrypt() expects it's first parameter to be a unsigned char*. reinterpret_cast<unsigned char*>(&(*it)) is a type cast, casting 'it' into a unsigned char*. 'it' is an iterator of an unspecified type (at least in the code provided) and the keyword 'auto' is used to initialize 'it' as the correct type automatically. reinterpret_cast<unsigned char*>(&(*it)) is then used to convert it unsigned char* automatically.

What is {{0}}, BF_set_key(&key, 16, key_data);?

This is used to initialize BF_KEY 'key', and then set the key using the value of key_data. This has no PHP equivalent, mcrypt will set the key internally.

Am I on the right track with the translation of the transform?

By the looks of it, the C++ version handles padding in an odd way. This could be intentional to throw a wrench into cracking attempts. Translating into PHP is not really possible unless you fully understand the algorithm of the original C++ - not just the encryption algo, but the full process being used, including pre and post encryption.

Have you considered making a simple PHP extension using the existing C/C++ code rather than converting to PHP? This should be very strait forward, much easier than converting a more complicated algorithm from C++ into PHP. The existing code can more or less be copy-pasted into an extension, with buffer_t &replay_data likely being registered as a PHP resource.

Would this be helpful, using the php-cpp library (see http://www.php-cpp.com ):

/**
 *  Decrypt function made accessible from PHP
 */

/**
 *  Dependencies
 */
#include <phpcpp.h>
#include <openssl/blowfish.h>
#include <algorithm>

/**
 *  Define buffer_t to be a vector
 */
typedef std::vector<uint8_t> buffer_t;

/**
 *  Function that should be ported to PHP
 *  @param  data
 *  @param  key_data
 */
static void decrypt(std::string &replay_data, const unsigned char *key_data) {  
    /*\ 
    |*| Performs an in place decryption of the replay using the given key.
    |*| The decryption is a (broken) variant of CBC decryption and is performed as follows:
    |*| -# Set the variable previous_block (with size of 16 bytes) to 0
    |*| -# Decrypt a block with the given key
    |*| -# XOR the block with the previous (decrypted) block
    |*| -# Go back to step 2, until there are no more blocks.
    \*/
    BF_KEY key = {{0}};
    BF_set_key(&key, 16, key_data);

    const int block_size = 8;

    size_t padding_size = (block_size - (replay_data.size() % block_size));
    if (padding_size != 0) {
        size_t required_size = replay_data.size() + padding_size;
        replay_data.resize(required_size, 0);
    }

    unsigned char previous[block_size] = {0};
    for (auto it = replay_data.begin(); it != replay_data.end(); it += block_size) {
        unsigned char decrypted[block_size] = { 0 };
        BF_ecb_encrypt(reinterpret_cast<unsigned char*>(&(*it)), decrypted, &key, BF_DECRYPT);
        std::transform(previous, previous + block_size, decrypted, decrypted, std::bit_xor<unsigned char>());
        std::copy_n(decrypted, block_size, previous);
        std::copy_n(decrypted, block_size, reinterpret_cast<unsigned char*>(&(*it)));
    }

    if (padding_size != 0) {
        size_t original_size = replay_data.size() - padding_size;
        replay_data.resize(original_size, 0);
    }
}

/**
 *  The PHP function that will take care of this
 *  @param  parameters
 *  @return Value
 */
static Php::Value php_decrypt(Php::Parameters &params)
{
    // check number of parameters
    if (params.size() != 2) throw Php::Exception("2 parameters expected");

    // read in the parameters
    std::string replay_data = params[0];
    std::string key_data = params[1];

    // decrypt it
    decrypt(replay_data, (const unsigned char *)key_data.c_str());

    // return the result
    return replay_data;
}

/**
 *  Symbols are exported according to the "C" language
 */
extern "C" 
{
    // export the "get_module" function that will be called by the Zend engine
    PHPCPP_EXPORT void *get_module()
    {
        // create extension
        static Php::Extension extension("my_decrypt","1.0");

        // add custom function
        extension.add("my_decrypt", php_decrypt);

        // return the extension module
        return extension.module();
    }
}

You can compile the code using the following command:

g++ -std=c++11 -fpic -shared my_decrypt.cpp -o my_decrypt.so -lphpcpp

The my_descript.so should be copied to you PHP extensions directory, and an "extension=my_decrypt.so" line should be added to your php.ini.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM