skip to Main Content

We use the Sage Pay / Opayo form integration to pass customer orders to Sage Pay for payment, then to process the report that comes back.
Sage Pay’s example code relies on the PHP mcrypt functions which are no longer supported from PHP 7.2, so we need to update the scripts to OpenSSL.

Encryption was covered by a great post here:
Upgrade mcrypt to OpenSSL encryption in SagePay form

  • this works fine for me:
    function encryptAes_new ($string, $key) {
      $key = str_pad($key,16,""); # if supplied key is, or may be, less than 16 bytes
      $crypt = openssl_encrypt($string, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $key);
      // Perform hex encoding and return.
      return "@" . strtoupper(bin2hex($crypt));
    }

…but I can’t find a matching code to fix the decrypt function.

The current code is:


    function decryptAes($strIn, $password)
    {
        // HEX decoding then AES decryption, CBC blocking with PKCS5 padding.
        // Use initialization vector (IV) set from $str_encryption_password.
        $strInitVector = $password;
    
        // Remove the first char which is @ to flag this is AES encrypted and HEX decoding.
        $hex = substr($strIn, 1);
    
        $strIn = pack('H*', $hex);
    
        // Perform decryption with PHP's MCRYPT module.
        $string = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, $strIn, MCRYPT_MODE_CBC, $strInitVector);
        return removePKCS5Padding($string);
    }

Could anyone help with an OpenSSL version, please?

2

Answers


  1. When decrypting, the @ prefix must first be removed, then hex decoding must be done, and finally decryption can be performed. openssl_decrypt() implicitly removes the padding, so that no explicit removal is required. This is implemented in decryptAes_new() below:

    <?php
    function encryptAes_new ($string, $key) {
        $key = str_pad($key,16,""); # if supplied key is, or may be, less than 16 bytes
        $crypt = openssl_encrypt($string, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $key);
        // Perform hex encoding and return.
        return "@" . strtoupper(bin2hex($crypt));
    }
    
    function decryptAes_new($string, $key) {
        $key = str_pad($key,16,""); 
        $binary = hex2bin(substr($string, 1));
        return openssl_decrypt($binary, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $key);
    }
    
    $plaintext = 'The quick brown fox jumps over the lazy dog';
    $key = '0123456789012345';
    $ciphertext = encryptAes_new($plaintext, $key);
    $decrypted = decryptAes_new($ciphertext, $key);
    print('Ciphertext: ' . $ciphertext . PHP_EOL);
    print('Plaintext:  ' . $decrypted . ' - Size: ' . strlen($decrypted) . PHP_EOL);
    ?>
    

    which produces the output:

    Ciphertext: @3089E6BC224BD95B85CF56F4B967118AAA4705430F25B6B4D953188AD15DD78F3867577E7D58E18C9CB340647C8B4FD8
    Plaintext:  The quick brown fox jumps over the lazy dog - Size: 43
    

    Checking the length of the plaintext after decryption, 43 bytes, shows that the padding has been automatically removed.

    Login or Signup to reply.
  2. Thank you, Mike, for asking the question and user16205441 for your answer.
    I found the answer was applicable to my problem.
    I have been trying for weeks to do the following:
    the string must be encrypted using AES (block size 128-bit) in CBC mode with PKCS#5 padding. Use the provided password as both the key and initialisation vector and encode the result in hex (making sure the letters are in upper case).

    When I operated the following code:

    <?php
    
    function encryptAes_new ($string, $key) {
        $key = str_pad($key,16,""); # if supplied key is, or may be, less than 16 bytes
        $crypt = openssl_encrypt($string, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $key);
        // Perform hex encoding and return.
        return "@" . strtoupper(bin2hex($crypt));
    }
    
    function decryptAes_new($string, $key) {
        $key = str_pad($key,16,""); 
        $binary = hex2bin(substr($string, 1));
        return openssl_decrypt($binary, 'aes-128-cbc', $key, OPENSSL_RAW_DATA, $key);
    }
    
    $plaintext = 'theLongStringIwasTryingToSend';
    
    $key = 'abcdefghijklmnop';
    
    $ciphertext = encryptAes_new($plaintext, $key);
    
    $decrypted = decryptAes_new($ciphertext, $key);
    
    print('Ciphertext: ' . $ciphertext . PHP_EOL);
    
    print('Plaintext:  ' . $decrypted . ' - Size: ' . strlen($decrypted) . PHP_EOL);
    
    ?>
    

    It produced the encrypted version the Opayo team were saying my string should have produced.

    I am a little surprised as the Opayo team mention the initialisation vector ($iv) which user16205441’s answer doesn’t mention explicitly. But if it works, that’s all that matters to me.

    I shall now try the form integration process again and see what the team says!

    All the best

    NeverSayDai

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search