Search code examples
phpc++opensslblowfishcbc-mode

OPENSSL Blowfish CBC encryption differs from PHP to C++


I am trying to encrypt and decrypt a communication between a C++ library and a PHP server using OPENSSL library in both of them. I want to use the Blowfish CBC algorithm but it seems that the results are different between the C++ code and the PHP code. The C++ code is taken from here:

This is the PHP code:

<?php
function strtohex($x) 
{
    $s='';
    foreach (str_split($x) as $c) $s.=sprintf("%02X",ord($c));
    return($s);
} 


$encryptedMessage = openssl_encrypt("input", "BF-CBC", "123456", 0, "initvect");


echo $encryptedMessage;
echo "\n";
echo strtohex($encryptedMessage);

The PHP output is this:

x9jDa2WMwvQ=
78396A446132574D7776513D

This is the c++ code:

bool do_encrypt(const char *in, unsigned char *out, int *outlen, unsigned    char *key, unsigned char *iv)
{
    int buflen, tmplen;

    EVP_CIPHER_CTX ctx;
    EVP_CIPHER_CTX_init(&ctx);
    EVP_EncryptInit_ex(&ctx, EVP_bf_cbc(), nullptr, key, iv);

    if (!EVP_EncryptUpdate(&ctx, out, &buflen, (unsigned char*)in,     strlen(in)))
    {
        return false;
    }

    if (!EVP_EncryptFinal_ex(&ctx, out + buflen, &tmplen))
    {
        return false;
    }

    buflen += tmplen;
    *outlen = buflen;
    EVP_CIPHER_CTX_cleanup(&ctx);

    return true;
}

unsigned char output[2048] = { 0 };
int outLen;

auto result = do_encrypt("input", output, &outLen, (unsigned char*)"123456", (unsigned char*)"initvect");

BIGNUM *outputStr = BN_new();
BN_bin2bn(output, outLen, outputStr);

cout << base64_encode(output, outLen) << "\n";
cout << BN_bn2hex(outputStr) << "\n";

The C++ output is this:

 EfRhhWqGmSQ=
 11F461856A869924

Can someone please help me understand what I'm doing wrong? Any help will be very much appreciated.

Thanks!

Edit 1: I managed to fix the C++ code after jww's answer and it worked well. I was missing the EVP_CIPHER_CTX_set_key_length However, I couldn't make the PHP code return the same thing and eventually we decided to move to AES and it now works flawlessly. Thanks!


Solution

  • For your OpenSSL code, I believe you need to call EVP_CIPHER_CTX_set_key_length to tell the library the key is only 6 bytes.

    Let me throw Crypto++ into the arena below. OpenSSL and Crypto++ will converge on the right answer once you add the missing EVP_CIPHER_CTX_set_key_length OpenSSL call. The right answer is 32CEBA7E046431EB (in hex).

    I don't know what's going on with PHP:

    x9jDa2WMwvQ=
    78396A446132574D7776513D
    

    Considering x is ASCII 0x78, 9 is ASCII 0x39, I'm guessing you hex encoded the Base64 string.


    $ cat test.cxx
    #include "blowfish.h"
    #include "modes.h"
    #include "channels.h"
    #include "filters.h"
    #include "base64.h"
    #include "hex.h"
    using namespace CryptoPP;
    
    #include <iostream>
    #include <string>
    using namespace std;
    
    int main(int argc, char* argv[])
    {
      const byte key[] = "123456";   // 6
      const byte  iv[] = "initvect"; // 8
      CBC_Mode<Blowfish>::Encryption encryptor;
      encryptor.SetKeyWithIV(key, 6, iv, 8);
    
      string r1, r2;
      ChannelSwitch chsw;
    
      Base64Encoder e1(new StringSink(r1));
      HexEncoder e2(new StringSink(r2));
      chsw.AddDefaultRoute(e1);
      chsw.AddDefaultRoute(e2);
    
      string msg = "input";
      StringSource ss(msg, true,
        new StreamTransformationFilter(encryptor,
          new Redirector(chsw)));
    
      cout << r1 << endl;
      cout << r2 << endl;
    
      return 0;
    }
    

    The test program results in:

    $ ./test.exe
    Ms66fgRkMes=
    32CEBA7E046431EB
    

    Here's the OpenSSL portion of things. Notice EVP_EncryptInit_ex is called twice. First, EVP_EncryptInit_ex is called to set the block cipher EVP_bf_cbc. The key length is set with EVP_CIPHER_CTX_set_key_length. Then second, EVP_EncryptInit_ex is called to set the key and iv.

    #include <openssl/evp.h>
    
    #include <iostream>
    #include <iomanip>
    #include <stdexcept>
    using namespace std;
    
    typedef unsigned char byte;
    
    int main()
    {
      EVP_CIPHER_CTX ctx;
      EVP_CIPHER_CTX_init(&ctx);
    
      int rc;
      const byte key[] = "123456";   // 6
      const byte  iv[] = "initvect"; // 8
    
      rc = EVP_EncryptInit_ex(&ctx, EVP_bf_cbc(), NULL, 0, 0);
      if (rc != 1)
        throw runtime_error("EVP_EncryptInit_ex failed");
    
      rc = EVP_CIPHER_CTX_set_key_length(&ctx, 6);
      if (rc != 1)
        throw runtime_error("EVP_CIPHER_CTX_set_key_length failed");
    
      rc = EVP_EncryptInit_ex(&ctx, NULL, NULL, key, iv);
      if (rc != 1)
        throw runtime_error("EVP_EncryptInit_ex failed");
    
      const byte msg[] = "input";
      byte buf[32];
      int len1 = sizeof(buf), len2 = sizeof(buf);
    
      rc = EVP_EncryptUpdate(&ctx, buf, &len1, msg, 5);
      if (rc != 1)
        throw runtime_error("EVP_EncryptUpdate failed");
    
      rc = EVP_EncryptFinal_ex(&ctx, buf+len1, &len2);
      if (rc != 1)
        throw runtime_error("EVP_EncryptFinal_ex failed");
    
      for(unsigned int i=0; i<len1+len2; i++)
          cout << std::hex << setw(2) << setfill('0') << (int)buf[i];
      cout << endl;
    
      EVP_CIPHER_CTX_cleanup(&ctx);
      return 0;
    }