Sagepay/Opayo PHP 7.4 integration without mcrypt - php

I use a PHP class to generate the crypt field for SagePay/Opayo but it uses mcrypt. My hosting company has now upgraded my webserver to PHP7.4 and mcrypt is no longer supported.
Ideally I'd like to just replace the following two functions with functions that don't use mcrypt (but do the same thing):
protected function encryptAndEncode($strIn) {
$strIn = $this->pkcs5_pad($strIn, 16);
return "#".bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptPassword, $strIn, MCRYPT_MODE_CBC, $this->encryptPassword));
}
protected function decodeAndDecrypt($strIn) {
$strIn = substr($strIn, 1);
$strIn = pack('H*', $strIn);
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->encryptPassword, $strIn, MCRYPT_MODE_CBC, $this->encryptPassword);
}
*the encryptPassword is my SagePay/Opayo encryption password.
SagePay used to provide example code but they don't do that anymore. They say the crypt string must be encrypted using AES (block size 128-bit) in CBC mode with PKCS#5 padding. Use the provided encryption password as both the key and initialisation vector and encode the result in hex (making sure the letters are in upper case).
Prepend the ‘#’ sign to the beginning of the encoded result.
To decrypt, ensure you remove the ‘#’ sign before using the same procedure in decryption mode.
I did find two similar posts on the forum (Sagepay integration without mcrypt and Sage Pay / Opayo Form integration - replacing mcrypt with openssl), both used openssl but the answer was specific to the code provided by the poster and I couldn't figure out how to modify it to suit my two functions.
If I try the existing code I get this error:
Fatal error: Uncaught Error: Call to undefined function mcrypt_encrypt()
I hope someone can help.
Thanks,

Related

in PHP, is it possible to decrypt a string which is encrypted by mcrypt_encrypt [duplicate]

Since mcrypt was deprecated in PHP 7.1 and I have a lot of data encrypted/decrypted with mcrypt in existing project, how to migrate my PHP code from mcrypt to OpenSSL? I have the following code to encrypt:
$encoded = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, 'salt', 'source string', MCRYPT_MODE_ECB));
And decryption code is:
$source = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, 'salt', base64_decode('encoded string'), MCRYPT_MODE_ECB);
What openssl_ functions should I use in the above examples to get the same results without encoded data conversion?
Or the only way is to run a script which will decrypt all my stored encrypted data with mcrypt and encode with openssl?
Thanks
OpenSSL doesn't have the Rijndael-256 cipher; there's no equivalent - you'll have to decrypt and re-encrypt everything.
But also:
You're missing padding and authentication.
Don't use ECB mode.
"salt" is not a proper encryption key, nor is any regular string. Use random_bytes() to generate your keys, with the proper key length for the chosen algorithm.
All of the above can be summed up like this: don't do it on your own, use a well-vetted library like defuse/php-encryption.
Cryptography is no simple thing and you can't do it properly with just 5 lines of code.

SagePay v3.0 VSPForm in PHP

I am using VSPForm on V3.00 and AES encryption. I have it all set up and working on one site but on another (where everything is identical) I get an error saying Currency field is missing.
After spending all day trying to sort it with Sage they keep telling me that I am not sending 128 bit encryption and that they cannot decrypt what i am sending. Strange as i am sending the same identical info from another site and that works. I know I am sending 128bit and I can encrypt and decrypt the string sent to them on my own system
For my encrypt I use the following inside a function
global $strEncryptionType
,$strEncryptionPassword;
$strIV = $strEncryptionPassword;
//** add PKCS5 padding to the text to be encypted
$strIn = addPKCS5Padding($strIn);
//** perform encryption with PHP's MCRYPT module
$strCrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $strEncryptionPassword, $strIn, MCRYPT_MODE_CBC, $strIV);
//** perform hex encoding and return
return "#" . bin2hex($strCrypt);
Does anyone know of any other reason why this error would occur? When checking the post and cart info Currency is definitely set.
The 3045 'Currency' error is usually nothing to do with the currency. It usually indicates that there is something wrong with the encryption - I would check the password is correct, bearing in mind that these are different for live / test.
I found the issue, for some reason my global vars are not working inside the function even though they are all on the same page
P

IV too long in PHP mcrypt_generic_init

I am working on a project where all the data from the web services is being encrypted using Triple DES Encryption. In my specific case, I am receiving a query string from a URL that has been encrypted. The web service provider has given me two values for decryption: the encryption Key_192 and the initialization vector IV_192. Both these keys are 24 characters long.
When I attempt to decrypt the query string I have received in PHP, I am using the mcrypt library. When initializing the generic decrypt methods, part of my function is:
$key = "XXXXXXXXXXXXXXXXXXXXXXXX";
$iv = "YYYYYYYYYYYYYYYYYYYYYYYY";
$cipher = mcrypt_module_open(MCRYPT_3DES, '', 'cbc', '');
mcrypt_generic_init($cipher, $key, $iv);
$result = rtrim(mdecrypt_generic($cipher, $this->hex2bin($buffer)), "\0");
mcrypt_generic_deinit($cipher);
return $result;`
However, when I execute that portion of my code, I receive the following message:
mcrypt_generic_init(): Iv size incorrect; supplied length: 24, needed: 8
The web services provider was not able to provide any guidance on the error, instead directing me to their VB.NET implementation which has a line like:
Dim cs As CryptoStream = New CryptoStream(ms, cryptoProvider.CreateDecryptor(KEY_192, IV_192), CryptoStreamMode.Read)
where they pass the two keys in directly, similar to the mcrypt_generic_init() function.
I understand that the IV size is dependent upon the cypher method (Triple DES), but am confused as to why I have an IV longer than the function appears to support. How could that be? My experience with this kind of encryption is limited, and I have been unable to decrypt the query string into anything that doesn't look like a field of random characters.
Run mcrypt_enc_get_iv_size() to figure out the required IV size. For Triple DES, it will be 8. mcrypt_generic_init() requires a string of exactly the correct length, so you should use a shorter string (or, to do it on the fly, use substr()).
It turns out that my issue in decrypting was caused by differences in how the mcrypt PHP library and the VB.NET libraries pad strings while encrypting. Also, with regard to the original question, only the first 8 characters of the IV are actually used. The others are discarded. Better description is located here:
http://mishu666.wordpress.com/2007/08/20/problem-and-solve-of-3des-incompatibilities-with-nets-tripledescryptoserviceprovider/

Base64 and mcrypt_encrypt in android

I need to encrypt in android a certain text using a secret key. In PHP the encryption code looks like this
$this->securekey = hash('sha256',$textkey,TRUE);
$this->iv = mcrypt_create_iv(32, MCRYPT_DEV_URANDOM);
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $this->securekey, $input, MCRYPT_MODE_ECB, $this->iv));
For Base64 I added the commons codec from apache.org (commons-codec-1.6.jar) in Netbeans for my Android application. There is no error in the code. But when I run the application and call the function that use the codec the application stop and need a fore close.
In the logCat says:
Android Runtime: java.lang.NoSuchMethodError:
org.apache.commons.codec.binary.Base64.decodeBase64
Here is my code :
public static String crypt(String input, String key){
byte[] crypted = null;
try{
SecretKeySpec skey = new SecretKeySpec(org.apache.commons.codec.binary.Base64.decodeBase64(key), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skey);
crypted = cipher.doFinal(input.getBytes());
}catch(Exception e){
}
return org.apache.commons.codec.binary.Base64.encodeBase64String(crypted);
}
I am not sure if my code do the same encryption as the PHP code. I found this link http://www.androidsnippets.com/encrypt-decrypt-between-android-and-php between Android and PHP but it doesn't use Base64, just for mcrypt_encrypt. Can anyone help me to obtain the same encryption as the PHP server.
Thanks in advance.
Your error is simply because you forgot to add the Apache codec library to your runtime environment. Just compiling against it is not enough; the library needs to be actually present on the Android device.
You cannot get the same encryption on Android using the default Java libraries, you probably require the Bouncy Castle library. The PHP code in your example uses Rijndael with a block size of 32 bytes. AES is a subset of Rijndael with block sizes of 16 bytes. This is known as MCRYPT_RIJNDAEL_128 in PHP mcrypt.
Some other implementation details:
ECB does not use an IV (by now I've replaced the default mcrypt_encrypt sample by something better);
mcrypt_encrypt does not perform PKCS5Padding, I think it uses spaces;
input.getBytes() is not portable, it uses the platform default encoding, which may be different from the PHP encoding;
Finally some security warnings:
just using SHA-256 on passwords is considered insecure, use PBKDF2;
ECB is considered insecure, use CBC;
MCRYPT_DEV_URANDOM is not secure (which basically means PHP encryption is worthless, you are better off using a PHP openssl wrapper);
Good luck!

Can I replicate the exact behaviour of PHP's AES encryption in ruby?

I'm in the process of rebuilding a PHP web app in Ruby on Rails, and would dearly love to avoid forcing all existing users to reset their encrypted passwords. The PHP site uses mcrypt_encrypt with AES-256-ECB, and I can't for the life of me get the same cipher text using ruby's OpenSSL. I can't decrypt them either (which is good in principle) since what's actually stored in the user DB is an MD5 hash of the AES cipher text.
I've read these previous, closely related questions and the very helpful answers:
How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together
Part II: How to make Ruby AES-256-CBC and PHP MCRYPT_RIJNDAEL_128 play well together
including the pages referenced there, and if I understand correctly, the PHP and ruby implementations use different padding methods. Since I have to live with how things work on the PHP side, is there any way to force the same padding method on ruby/OpenSSL somehow? I'm using ruby 1.9.2-p180.
Here's the sample code in PHP:
$salt = "12345678901234567890123456789012";
$plain = "password";
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$cipher = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $salt, $plain, MCRYPT_MODE_ECB, $iv);
echo md5($cipher);
Output: 6337137fd88148250fd135a43dbeb84a
and in ruby:
require 'openssl'
salt = "12345678901234567890123456789012"
plain = "password";
c = OpenSSL::Cipher.new("AES-256-ECB")
c.encrypt
c.key = salt
cipher = c.update(plain)
cipher << c.final
puts Digest::MD5.hexdigest(cipher)
Output: 18dee36145c07ab83452aefe2590c391
Actually not in general an openssl solution but maybe it is ok for you to have a working example.
require 'mcrypt'
require 'openssl'
plaintext = 'password'
puts plaintext
key = '12345678901234567890123456789012'
enc = Mcrypt.new(:rijndael_256, :ecb, key, nil, :zeros)
encrypted = enc.encrypt(plaintext)
puts Digest::MD5.hexdigest(encrypted)
I used an additional gem(ruby-mcrypt). Seems to be an issue with openssl. Actually the issue seems to be that Openssl does not support zero padding and uses either no-padding or default-openssl-padding. Due to the fact that you use zero padding in php you must use zero padding also in ruby.
Output on my machine for the php script:
[~/test] ➔ php5 t.php
6337137fd88148250fd135a43dbeb84a
and for the ruby script:
[~/test] ➔ ruby t2.rb
password
6337137fd88148250fd135a43dbeb84a
and my ruby version:
[~/test] ➔ ruby -version
ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux]
Hope this helps.
if key size is not standard on php side, you need to fill the key with zeros to next valid key size, in order to make ruby side works like this:
php_encrypted = string_encoded_with_php_mcrypt
key = "longerthan16butnot24".to_a.pack('a24')
enc = Mcrypt.new(:rijndael_256, :ecb, key, nil, :zeros)
enc.decrypt(php_encrypted)
In this case next valid key length is 24.
For :rijndael_256 valid key lengths are: 16, 24, 32
You can get more info on algorithms:
Mcrypt.algorithm_info(:rijndael_256
if you can use other encrypt methods, you can try TEA Block Encryption. I have adopted the method across Ruby, JS, ActionScript. It should work with PHP as well. github repo is here

Categories