Search code examples
phpstringencryptionmcrypt

PHP mcrypt - mcrypt crypts each string seperatly in concatenated string


I have currently started to work with PHP with a Java background and came accross some issues. I am using mcrypt for a basic encryption using mcrypt_encrypt ( string $cipher , string $key , string $data , string $mode [, string $iv ] ) the encryption works successfully but there is a case where I need to concatenate 2 strings and then encrypt them but when I do this the output is just as if I had encrypted each string separately then concatenated them afterwards and not before the encryption. What I am doing is this :

function base64url_encode($data) {
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}

function base64url_decode($data) {
    return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}

function encryptCode($data){
    return mcrypt_encrypt( MCRYPT_DES , '12345678' , $data , 'cbc' ,'87654321'); 
}

function decryptCode($data){
    return mcrypt_decrypt( MCRYPT_DES , '12345678' , $data , 'cbc' ,'87654321'); 
}

$id = 'Q2JmDpmqjNmGT4FJ2EkXXITOgc31ZA52';
$toAdd = 'hellothere';
$base64Decoded = base64url_decode($id);
$decrypted = decryptCode($base64Decoded);
$decrypted = $decrypted.$toAdd;
$encryptedID = encryptCode($decrypted);
$base64Encoded = base64url_encode($encryptedID);
print_r($base64Encoded);

and then the output is : Q2JmDpmqjNmGT4FJ2EkXXITOgc31ZA52DG4cvxVuJVnkcrINN0Zt9g

I am aware of the weakness of DES but I need it in this case so please no comments about that. Thanks to all for your help.


Solution

  • I can't say why mcrypt_encrypt is not respecting the CBC mode of operation (without seeing actual input and output, I'm assuming it's acting as ECB). Perhaps mcrypt's default null padding algorithm (0x00) is causing issues? I can say that mcrypt is abandonware for ~10 years now, so I wouldn't spend the energy trying to figure that out. Use libsodium, or, failing that, at least openssl.

    This post by Scott Arciszewski explains the issues with mcrypt in PHP.

    Update

    I ran your provided code and made some additional modifications to demonstrate that CBC mode was working as expected. Here is the code I ran:

    function base64url_encode($data) {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
    
    function base64url_decode($data) {
        return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
    }
    
    function encryptCode($data){
        return mcrypt_encrypt( MCRYPT_DES , '12345678' , $data , 'cbc' ,'87654321'); 
    }
    
    function decryptCode($data){
        return mcrypt_decrypt( MCRYPT_DES , '12345678' , $data , 'cbc' ,'87654321'); 
    }
    
    $id = 'Q2JmDpmqjNmGT4FJ2EkXXITOgc31ZA52';
    $base64Decoded = base64url_decode($id);
    $decrypted = decryptCode($base64Decoded);
    print_r($decrypted."\n");
    print_r("\n\n");
    
    # Make the new plaintext string
    $toAdd = 'hellothere';
    $additionalCipherText = encryptCode($toAdd);
    $additionalEncoded = base64url_encode($additionalCipherText);
    print_r("Additional cipher text: ".$additionalEncoded."\n");
    print_r("\n\n");
    
    # Concatenate the plaintext and encrypt
    $plaintext = $decrypted.$toAdd;
    $cipherText = encryptCode($plaintext);
    $base64Encoded = base64url_encode($cipherText);
    print_r("     New cipher text: ".$base64Encoded."\n");
    print_r("Original cipher text: ".$id.$additionalEncoded."\n");
    print_r("\n\n");
    
    # Try the reverse order
    $plaintext = $toAdd.$decrypted;
    $cipherText = encryptCode($plaintext);
    $base64Encoded = base64url_encode($cipherText);
    print_r("     New cipher text: ".$base64Encoded."\n");
    print_r("Original cipher text: ".$additionalEncoded.$id."\n");
    

    I received the following output:

    ��t
    anEncryptedId
    
    
    Additional cipher text: paTJPP5mr-65c1OKybvB1A
    
    
         New cipher text: Q2JmDpmqjNmGT4FJ2EkXXITOgc31ZA52DG4cvxVuJVnkcrINN0Zt9g
    Original cipher text: Q2JmDpmqjNmGT4FJ2EkXXITOgc31ZA52paTJPP5mr-65c1OKybvB1A
    
    
         New cipher text: paTJPP5mr-69piYC2Ep0BM1tiph63ZFqdg_whovwRh0-4AD37H2JPQ
    Original cipher text: paTJPP5mr-65c1OKybvB1AQ2JmDpmqjNmGT4FJ2EkXXITOgc31ZA52
    

    As you can see, encrypting just $toAdd by itself and concatenating with the provided cipher text is not the same as concatenating the plaintexts and encrypting. You can see that the 33rd character of the Base64-encoded output is where the differences start, which makes sense: the 33rd character of Base64 is the start of the 25th byte of the cipher text. In DES, the block size is 64 bits / 8 bytes, so the first three blocks will be encrypted identically. The following block would be modified by the ^ operation of the previous block's cipher text where the IV would otherwise be used.

    I repeated this operation with the opposite order of the two plaintext inputs. Again, I saw that the first 12 characters of the Base64 output (12 chars -> 8 bytes) are identical, and then there are differences. This is expected, as the original plaintext does not end on a full block boundary, so the new "second block" is not identical to the original "second block". The CBC operation works successfully here to "jumble" the second block.

    I would double check the code around the handling of this data and ensure that you are performing the operations (and in the order) you think you are.