Search code examples
phpmcryptcryptojsphp-openssl

PHP Converting mcrypt to openssl


I know 3DES and MD5 are insecure. I will work on replacing them once I have it working again,

I have a mobile app that is using 3DES with an MD5 of a key as a SECRET KEY to talk to a PHP Application.

Now this code worked perfectly on PHP 5.3 (this is an example I have generated)

mcrypt_decrypt(
    MCRYPT_3DES, 
    md5(
        utf8_encode(
            "MobileAppSecureKey"
        ),
        true
    ), 
    base64_decode("bkCfcseIt/TPsgNCdyX9fv2/4MjOJdaPXakNNbxQT3n6tXHa5bDoXojQ3g7jPLCu+wjwD0guQzw3hCFUSVx47PmDNHASk7g/kJ4K4tX0VGI="), 
    MCRYPT_MODE_CBC, 
    base64_decode("cTOCJ/iYL18=")
)

Now I have ported it over to use OpenSSL method my new code is

openssl_decrypt(
    base64_decode("bkCfcseIt/TPsgNCdyX9fv2/4MjOJdaPXakNNbxQT3n6tXHa5bDoXojQ3g7jPLCu+wjwD0guQzw3hCFUSVx47PmDNHASk7g/kJ4K4tX0VGI="), 
    'DES-EDE3-CBC', 
    md5(
        utf8_encode(
            "MobileAppSecureKey"
        ),
        true
    ), 
    0, 
    base64_decode("cTOCJ/iYL18=")
)

But the new code does not work, It gives the error error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length from openssl_error_string()

I'm unsure why it's complaining about final block length when this worked perfectly in mcrypt

The code used to generate the test data was from the mobile the mobile app is a Cordova app and uses the CryptoJS library

key = CryptoJS.MD5(key);

// copy 3DES subkey 1 to the last 64 bit to make a full 192-bit key
key.words[4] = key.words[0];
key.words[5] = key.words[1];

if(typeof(iv) === "undefined"){
    iv = CryptoJS.lib.WordArray.random(8);
}

var encrypted = CryptoJS.TripleDES.encrypt(pt, key, {iv: iv});
return {"str":encrypted.toString(), "iv":CryptoJS.enc.Base64.stringify(iv)};

The Encrypted payload was

{"jsonrpc":"2.0","method":"events.enableParentGenres","params":[159],"id":1}

Modifications that have been tried,

As per yivi suggestion, options have been set to OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING

As per Tuckbros suggestion, the message has been padded

$message_padded = base64_decode("bkCfcseIt/TPsgNCdyX9fv2/4MjOJdaPXakNNbxQT3n6tXHa5bDoXojQ3g7jPLCu+wjwD0guQzw3hCFUSVx47PmDNHASk7g/kJ4K4tX0VGI=");
$message_padded = str_pad($message_padded,
        strlen($message_padded) + 8 - strlen($message_padded) % 8, "\0");

Both of these prevented the error about final block length however the encrypted payload when running through the code did not decrypt.


Solution

  • The parameters you are passing in openssl_decrypt seem to be wrong; you are passing the OPTIONS parameter as 0, which you have to set to OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING because you provide the function with raw data (base64_decode).

    The key also needs to be converted to a 192 bits key, as in the javascript code:

    $key = md5(utf8_encode("MobileAppSecureKey"), true);
    
    //key.words[4] = key.words[0];
    //key.words[5] = key.words[1];
    
    for($i = 0; $i < 8; $i++) {
        $key[$i + 16] = $key[$i];
    }
    

    Try this one out:

    $key = md5(utf8_encode("MobileAppSecureKey"), true);
    $data = base64_decode("bkCfcseIt/TPsgNCdyX9fv2/4MjOJdaPXakNNbxQT3n6tXHa5bDoXojQ3g7jPLCu+wjwD0guQzw3hCFUSVx47PmDNHASk7g/kJ4K4tX0VGI=");
    
    for($i = 0; $i < 8; $i++) {
        $key[$i + 16] = $key[$i];
    }
    
    $decoded = openssl_decrypt(
        $data,
        'DES-EDE3-CBC',
        $key,
        OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
        base64_decode("cTOCJ/iYL18=")
    );
    
    echo "\$decoded = {$decoded}";
    
    // Will output:
    // $decoded = {"jsonrpc":"2.0","method":"events.enableParentGenres","params":[159],"id":1}