SagePay v3.0 VSPForm in PHP - 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

Related

Sagepay/Opayo PHP 7.4 integration without mcrypt

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,

php from mcrypt to openssl

Since this day I've used mcrypt on my website to encrypt the users e-mail address.
The php mcrypt module was installed with php 7.4 on my last server so it didn't make any problems.
Since I needed to change the hosting company, they won't provide me with a mcrypt installation on their server. So I'll need to change the function which will be supported on php 7.4.
I had this function to encrypt my users e-mail address for security reasons if a sql injection ever happened.
function encrypt_128($string){
$string = rtrim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_ECB)));
return $string;
I've replaced with this function but it doesn't return the same output.
function encrypt_128($string){
return rtrim(base64_encode(openssl_encrypt($string, 'aes-256-ecb', $key, OPENSSL_RAW_DATA)));
I've read that mcrypt uses no padding and I tried adding OPENSSL_ZERO_PADDING but it can't encrypt the users e-mail address anymore, and returns no output.
I'll need the function to make the same output because the new users can register with the same e-mail address as the old users.
It turns out it isn't a way to make this possible
I just decrypted all my data and re encrypted them with the new algorithm. It took long but it was a needed change.
If anyone knows how feel free to post an answer.

Decrypt Rijndael with Passphrase in PHP

A customer is sending us a file encrypted with "AES-256". It arrives as a binary file (I normally get base64-encoded files, but this should be OK) and, in desperation, I have iterated through the PHP options using mcrypt_decrypt but cannot crack it.
<?php
$str = file_get_contents($argv[1]);
$key ='jimminny fred owns apple'; //not the actual one, but same length
$modes = array(
MCRYPT_MODE_ECB,
MCRYPT_MODE_CBC,
MCRYPT_MODE_CFB,
MCRYPT_MODE_OFB,
MCRYPT_MODE_NOFB);
$cryps = array(
MCRYPT_RIJNDAEL_128,
MCRYPT_RIJNDAEL_256,
MCRYPT_RIJNDAEL_192);
foreach($modes as $mode){
foreach($cryps as $cryp){
echo "\n\n$cryp $mode\n\n";
echo mcrypt_decrypt($cryp, $key, $str, $mode);
}
}
My understanding is that I should be receiving a 32-byte key, not a 24 char passphrase, but they are using a program called GlobalScape and this is all it requires for its 'Rijndael' encryption. (See screenshot attached showing the dialog window that the customer completes at their end).
I've checked the site and GlobalScape doesn't offer any details on how they encrypt. Not only do they not specify the key derivation function (KDF) that they use for "Rijndael" encryption. It's unlikely that they directly use the passphrase as a key - unless they are complete muppets - but the KDF could be anything. They do not specify the mode of operation for AES either
Simply do not use trash like that. Just use PGP or one of the standardized options for sending / receiving messages.
Going on a wild goose chase won't help you. Even if you program a solution then it may fail in the future because of any number of factors. Ask your client to clearly specify a protocol instead of giving you a screenshot.

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/

Decrypting the .ASPXAUTH Cookie WITH protection=validation

For quite sometime I've been trying to decipher the ASP .ASPXAUTH cookie and decrypt it using PHP. My reasons are huge and I need to do this, there is no alternative. In PHP so far I have successfully managed to read the data from this cookie, but I cannot seem to do it while it is encrypted. Anyway, here it goes...
First you need to alter your servers Web.config file (protection needs to be set to Validation):
<authentication mode="None">
<forms name=".ASPXAUTH" protection="Validation" cookieless="UseCookies" timeout="10080" enableCrossAppRedirects="true"/>
</authentication>
Then in a PHP script on the same domain, you can do the following to read the data, this is a very basic example, but is proof:
$authCookie = $_COOKIE['_ASPXAUTH'];
echo 'ASPXAUTH: '.$authCookie.'<br />'."\n";//This outputs your plaintext hex cookie
$packed = pack("H*",$authCookie);
$packed_exp = explode("\0",$packed);//This will separate your data using NULL
$random_bytes = array_shift($packed_exp);//This will shift off the random bytes
echo print_r($packed_exp,TRUE); //This will return your cookies data without the random bytes
This breaks down the cookie, or at least the unencrypted data:
Now that I know I can get the data, I removed the 'protection="validation"' string from my Web.config and I tried to decrypt it using PHP mcrypt. I have tried countless methods, but here is a promising example (which fails)...
define('ASP_DECRYPT_KEY','0BC95D748C57F6162519C165E0C5DEB69EA1145676F453AB93DA9645B067DFB8');//This is a decryption key found in my Machine.config file (please note this is forged for example)
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, ASP_DECRYPT_KEY, $authCookie, MCRYPT_MODE_CBC, $iv);//$authCookie is the pack()'d cookie data
This however fails. I've tried variations of IV with all zeros # 16 bytes. I've tried different Rijndael sizes (128 vs 256). I've tried base64_decode()ing, nothing seems to work. I've found this stackoverflow post here and started using variations of the key/iv that are made using sha256, but that isn't really working either.
Anybody have a clue what I should do?
I don't know how encryption is made in .NET AuthCookies, but I can try to answer.
Assuming the encryption occurs in AES CBC-IV mode, with randomly generated IVs, you need to first find out where the IV is.
The code snippet you show cannot work, as you are generating a random IV (which will be incorrect). That being said, even if you get the IV wrong, in CBC mode you will only have the first 16 bytes of your decrypted ciphertext "garbled" and the rest will decrypt properly - you can use this as a test to know if you're doing the rest correctly. In practice when using random IVs, it's very likely that it's prepended to the ciphertext. To check if this correct, you can try to check if len(ciphertext) = len(plaintext) + 16. This would mean that most likely the first 16 bytes are your IV (and therefore it should be removed from the ciphertext before attempting to decrypt it).
Also on your code snippet, it seems you are using the key as an ascii-string, whereas it should be a byte array. Try:
define('ASP_DECRYPT_KEY',hex2bin('0BC95D748C57F6162519C165E0C5DEB69EA1145676F453AB93DA9645B067DFB8'));
Also, this seems to be a 32 byte key, so you need to use AES-256. I don't know how the authcookie looks like, but if it's base64 encoded, you also need to decode it first obviously.
Hope this helps!
Note: I don't recomment doing this for important production code, however - because there are many things that can go wrong if you try to implement even your own decryption routine as you are doing here. In particular, I would guess there should be a MAC tag somewhere that you have to check before attempting decryption, but there are many other things that can go wrong implementing your own crypto.
I understand this may not have been possible for the OP but for other people heading down this route here is a simple alternative.
Create a .net web service with a method like:
public FormsAuthenticationTicket DecryptFormsAuthCookie(string ticket)
{
return FormsAuthentication.Decrypt(ticket);
}
Pass cookie to web service from PHP:
$authCookie = $_COOKIE['.ASPXAUTH'];
$soapClient = new SoapClient("http://localhost/Service1.svc?wsdl");
$params= array(
"ticket" => $authCookie
);
$result = $soapClient->DecryptFormsAuthCookie($params);
I know what a pain is to decrypt in PHP something encrypted in .NET and vice versa.
I had to end up coding myself the Rijndael algorithm ( translated it from another language ).
Here is the link to the source code of the algorithm: http://pastebin.com/EnCJBLSY
At the end of the source code there is some usage example.
But on .NET, you should use zero padding when encrypting. Also test it with ECB mode, I'm not sure if CBC works.
Good luck and hope it helps
edit: the algorithm returns the hexadecimal string when encrypts, and also expects hexadecimal string when decrypting.

Categories