Upgrade mcrypt to OpenSSL encryption in SagePay form - encryption

I have a Form which collects data, then submits this to SagePay, passing the data. This worked fine until we needed to update to PHP 7.2, and as mcrypt is no longer supported, we're switching to OpenSSL.
The form that gathers the data works fine but transporting the data doesn't encrypt and the transaction fails.
Code in functions file:
$protx_encryption_password = "my_password";
$protx_vendor = "my_vendor";
$data = "";
while(list($key,$value) = each($protx)) {
$data .= $key."=".$value."&";
}
$data = trim($data," &");
$crypt = openssl_encrypt($data, $protx_encryption_password);
function openssl_encrypt($string, $key) {
$iv = substr($value, 0, 16);
$ciphertext = substr($value, 16);
$key = hash('sha256', $key, true);
$crypt = openssl_encrypt(
$ciphertext, 'AES-256-CBC', $key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
$iv
);
return rtrim($crypt, "\0");
}
The $crypt data is sent in a hidden field as before in the mcrypt version, I just need some help in getting the encryption working.
Previously in the mcrypt way:
$crypt = encryptAes($data, $protx_encryption_password);
function encryptAes($string, $key) {
// AES encryption, CBC blocking with PKCS5 padding then HEX encoding.
// Add PKCS5 padding to the text to be encypted.
$string = addPKCS5Padding($string);
// Perform encryption with PHP's MCRYPT module.
$crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $key);
// Perform hex encoding and return.
return "#" . strtoupper(bin2hex($crypt));
}
function addPKCS5Padding($input) {
$blockSize = 16;
$padd = "";
// Pad input to an even block size boundary.
$length = $blockSize - (strlen($input) % $blockSize);
for ($i = 1; $i <= $length; $i++)
{
$padd .= chr($length);
}
return $input . $padd;
}
This is the first OpenSSL encryption I've done so I'd appreciate any assistance with this. SagePay needs to be encrypted using AES-128 encryption with 128-bit block size in CBC mode with PCKS#5 (AES-128-CBC-PKCS#5). Any help would be truly helpful.

First, this is a bad scheme. Using a (real) password as the key for modern cryptography, even where it is sort of possible, is almost always insecure. Passwords and keys are different things that are designed and specified differently. Of course some people who don't know what they're doing call something that is actually a key a password, and since I don't know SagePay (and of course you don't give a real value) I don't know if that's the case here. Further, using the key (or password) as IV is grossly nonstandard. The whole point of an IV is to be different for every encryption (or at least every data value) under a given key, while a given key by definition is the same as itself. This is so silly I don't recall seeing any analysis of whether and what attacks it enables, although as a general rule violating the 'security contract' usually does lead to problems. Using any fixed value for IV, a rather more common error, is only moderately bad for CBC mode, although it is much worse for some other modes.
However, you apparently don't have the option to change this scheme, and if you did, it would not really be a programming Q and would belong instead on security.SX or maybe crypto.SX depending.
Also FYI it is meangingless to say "AES-128 with a 128-bit block". AES always uses an 128-bit block; that was part of the rules of the competition that established it. Rijndael does have options for other block sizes, which is why mcrypt, which implements Rijndael more-or-less, specifies the block size, but OpenSSL implements AES, which does not need to and does not specify the block size -- only the key size.
Second, your proposed new code is nonsense. First you try to redefine a standard function, which is not allowed, and then the logic you propose was clearly designed to decrypt, not encrypt, a substantially different scheme, not the one you present, and then mangled.
mcrypt_encrypt zero-pads the plaintext, and in older versions the key and iv, as needed. For Rijndael it also chooses the key size based on the supplied key; since you say you want AES-128 (i.e. a 128-bit key) the so-called 'password' must not be more than 128-bits but may be less. The code you posted does explicit PKCS5 padding of the data, so mcrypt's zero padding isn't used, and as Peter commented, openssl_encrypt (and the OpenSSL routines it uses underneath) do PCKS5/7 padding by default, so all you need is:
function encryptAes_new ($string, $key) {
$key = str_pad($key,16,"\0"); # 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));
}
(Note: Original PKCS5 padding only handled 64-bit/8-byte blocks, and PKCS7 extended it to other sizes, but PKCS5v2.1 in 2017 also extended it referencing CMS which is the IETF version of PKCS7, so they are now the same.)

Related

Encryption with Crypto.js and pycrypto generates different encryption cipher text

I am trying to generate encrypted text in Node using Crypto.js using its AES algorithm. It works in Js for encryption and decryption both. Similarly I tried implementing same in Python using pycrypto and it does encryption and decryption both in python. But the problem arises when I want to use encrypted cipher text from JS to decrypt in Python. The problem is that the encrypted text generated in JS is different from what is generated in Python.
Here is the JS based usage of AES:
import CryptoJS from "crypto-js";
let str = "lol";
let salt = "ABCDEFGHIJKLMNOP";
let key = "ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP";
salt = CryptoJS.enc.Hex.parse(salt);
key = CryptoJS.enc.Hex.parse(key);
const options = {
iv: salt,
padding: CryptoJS.pad.ZeroPadding,
mode: CryptoJS.mode.CFB,
};
// function to encrypt the string
function encrypt(str, salt, key) {
const cipher = CryptoJS.AES.encrypt(str, key, options);
console.log("cipher", CryptoJS.enc.Hex.stringify(cipher.ciphertext));
return cipher.ciphertext.toString(CryptoJS.enc.Base64);
}
// function to decrypt the string
function decrypt(str, salt, key) {
const cipher = CryptoJS.AES.decrypt(str, key, options);
return cipher.toString(CryptoJS.enc.Utf8);
}
const encrypted = encrypt(str, salt, key);
console.log("encrypted", encrypted);
const decrypted = decrypt(encrypted, salt, key);
console.log("decrypted", decrypted);
// OUTPUT
// encrypted sd4Xpz/ws8x2j+cgF17t6A==
// decrypted lol
Here is the Python based usage of AES:
from Crypto.Cipher import AES
from base64 import b64encode, b64decode
str = "lol";
salt = b"ABCDEFGHIJKLMNOP";
key = "ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP";
enc_dec_method = 'utf-8'
def encrypt(str_to_enc, str_key, salt):
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt)
hx_enc = aes_obj.encrypt(str_to_enc.encode('utf8'))
df = b64encode(hx_enc)
mret = b64encode(hx_enc).decode(enc_dec_method)
return mret
def decrypt(str_to_dec, str_key, salt):
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt)
str_tmp = b64decode(str_to_dec.encode(enc_dec_method))
str_dec = aes_obj.decrypt(str_tmp)
mret = str_dec.decode(enc_dec_method)
return mret
test_enc_text = encrypt(str, key, salt)
test_dec_text = decrypt(test_enc_text, key, salt)
print(f"Encrypted Text: {test_enc_text}")
print(f"Decrypted Text: {test_dec_text}")
# OUTPUT
# Encrypted Text: o9XB
# Decrypted Text: lol
I tired encrypting and decrypting in both the languages and check the the documentation and source code on both the libraries.
Did also trying checking if I am missing encoding or decoding, but had no luck.
What key concept am I missing here which can help bridge the gap of compatibility here?
The codes are incompatible because:
Key and IV are not hex encoded, so the hex encoder must not be used in the CryptoJS code. If Utf-8 encoding is to be applied as in the Python code, the Utf-8 encoder must be used instead.
salt = CryptoJS.enc.Utf8.parse(salt);
key = CryptoJS.enc.Utf8.parse(key);
CFB is a stream cipher mode that does not require padding. However, in the current CryptoJS code, Zero padding is used (CryptoJS.pad.ZeroPadding). Instead, the padding has to be disabled (which does not happen implicitly, unlike in the Python code):
padding: CryptoJS.pad.NoPadding,
CFB is configured with an additional parameter, the segment size, which specifies the number of bits encrypted per encryption step. The CryptoJS code supports only one segment size, 128 bits. With PyCryptodome the segment size can be configured, by default 8 bits are used. For compatibility it must be changed to 128 bits:
aes_obj = AES.new(str_key.encode('utf-8'), AES.MODE_CFB, salt, segment_size=128)
With these changes both codes are compatible.
Test: Both codes provide the following ciphertext for the following plaintext:
plaintext: The quick brown fox jumps over the lazy dog
ciphertext: m0T/7e04eV49RTcgd7KtwHxSOavNzHNwlrvjt1YmmHidBy4rHS0oovclKQ==
Note that what you call salt is actually the initialization vector (IV). The term salt is more commonly used in the context of key derivation functions (like PBKDF2).
Regarding security: The values used for salt and IV are OK for testing, but in a real world scenario a random byte sequence has to be applied for the IV for each encryption, which has to be passed along with the ciphertext to the decrypting side (generally concatenated).
Also, as key no passphrase has to be used, but a random byte sequence. A passphrase (a strong one of course) may only be applied in combination with a key derivation function.

What is an openssl iv, and why do I need a key and an iv?

I am about to use the following script to encrypt and decrypt some data. I am using it because my current encryption does not work on our new server. We are currently using mcrypt so I want to change to openssl.
In our database we use aes encryption which uses a 128bit key so I know what a key is, but I do not know what an openssl iv is? And why would I need a key and an iv.
The code I am about to use is this, which I found on a website because I don't understand encryption very well.
Obviously I will modify it so that the key is kept somewhere else.
function encrypt_decrypt($action, $string) {
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = 'This is my secret key';
$secret_iv = 'This is my secret iv';
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
if( $action == 'encrypt' ) {
$output = openssl_encrypt($string, $encrypt_method, $key, 0, $iv);
$output = base64_encode($output);
}
else if( $action == 'decrypt' ){
$output = openssl_decrypt(base64_decode($string), $encrypt_method, $key, 0, $iv);
}
return $output;
}
$plain_txt = "This is my plain text";
echo "Plain Text = $plain_txt\n";
$encrypted_txt = encrypt_decrypt('encrypt', $plain_txt);
echo "Encrypted Text = $encrypted_txt\n";
$decrypted_txt = encrypt_decrypt('decrypt', $encrypted_txt);
echo "Decrypted Text = $decrypted_txt\n";
if( $plain_txt === $decrypted_txt ) echo "SUCCESS";
else echo "FAILED";
echo "\n";
The Initialization Vector is part of what makes AES in CBC (Cipher Block Chaining) mode work - IVs are not unique to OpenSSL. CBC works by XORing the previous block with the current block. The very first block has no previous block, so the IV serves that purpose.
Why this is necessary requires a bit of understanding of how block ciphers work. Without this chaining and IV, we're left with a mode of AES called ECB, or Electronic Code Book. ECB has weaknesses that allow a chosen plaintext attack, among many other problems.
I would recommend spending a bit of time with best practices for CBC initialization vectors. Using them incorrectly can weaken the overall security of AES. The short explanation is:
IVs should be random and generated by a CSPRNG.
IVs should not be reused. That is, don't encrypt plaintext "A" and plaintext "B" with the same IV. Every record should have its own IV.
The IV is not a secret like the key. It can be stored in plaintext along with the cipher text.
Also keep in mind that this advice only applies to AES-CBC. If you ever investigate other modes of AES, such as GCM, this does not apply.
Let's say two users have the password "princess", and you encode them with the key "some-key", they will both have the same encrypted result:
| User | Encrypted password |
|------------|-----------------------|
| a | sN7vIFg= |
| b | sN7vIFg= |
Which means that if someone figures out using a different means that user a's real password is "princess" and their encrypted password is "sN7vIFg=", then any user with the encrypted password "sN7vIFg=" can be deemed to have the password "princess".
However if you use a random IV for each, then it is harder to guess the underlying passwords if you have access to the encrypted data.
| User | Encrypted password | IV |
|------------|-----------------------|----------------|
| a | sN7vIFg= | h²ÓIF8]h%L |
| b | BjZAzrA= | VÂøíiøÚØò▓ |
Now, even if someone has access to the above data, they can't figure out that users a and b have the same password.
See also Is “real salt” the same as “initialization vectors”?
.
I think you maybe mixed up "hashed key" and "iv" (God knows I did when starting crypto). hashed key is exactly what you did. For iv you should use different random data each time encryption is made with the same key. (my background: I had to build a pdo secure session handler which encrypts/decrypts the session data so I ended up implementing AES-256-CBC using openssl extension)
just a small tip if anyone gets here.
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$iv = substr(hash('sha256', $secret_iv), 0, 16);
is not the right way of generating iv + no need for secret_iv because iv has to be as random as it gets. Don't treat it (or understand it) the same as hashing.
As you have access to openssl extension there is a better/safer way of generating iv for the chosen cipher, openssl can also tell you the right length of the iv for the cipher:
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('AES-256-CBC'));
It will be in binary format so if you need it in hex use bin2hex($iv).
If you need to store the generated iv in mysql I store it in raw format (field type varbinary, binary also works).
One more thing. You have $options tag set to 0 in both openssl_encrypt and _decrypt which means "if set to true will return as raw output data, otherwise the return value is base64 encoded".
I'd like to offer a simple explanation with reference to the quesion in the topic- why do you need a key as well as IV? In other words, why a mere key is not enough for the encryption? (general answer, not referring to a specific algorithm)
First lets describe what's happening in block cipher- A block cipher get a key and text as input. Then it divides the text into same sized blocks and encrypts each one of them seperately. Problem with this approach is that given a key K- same plaintext blocks generates same ciphertext blocks. This obviously is undesirable since it makes patterns which are easier to identify by an attacker.
To avoid those patterns a solution would be to use previous ciphertext block and concatenate it to the current block when encrypting current block. This way there are no patterns.
However- what do you concatenate to the first block? here comes the IV- Initialization vector. This servers as a initial value which can be used as the missing ciphertext block you concatenate to the first block of plaintext.
I usually set up something like this:
// PHP Function to encrypt
function encrypt_AES_CBC ($plaintext) {
$method = "aes-256-cbc";
$key = "DADA068D255130F7AB5965BD47692E359EE7B0CBC81D9DAD42665B71F7807A4E";
$bkey = hex2bin($key);
$iv = hex2bin(md5(microtime().rand()));
$data = openssl_encrypt($plaintext, $method, $bkey, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv.$data);
}
// PHP Function to decrypt
function decrypt_AES_CBC ($encryptedText) {
$method = "aes-256-cbc";
$key = "DADA068D255130F7AB5965BD47692E359EE7B0CBC81D9DAD42665B71F7807A4E";
$bkey = hex2bin($key);
$decoded = base64_decode($encryptedText);
$iv = substr($decoded, 0, 16);
$data = substr($decoded, 16);
return openssl_decrypt( $data, $method, $bkey, OPENSSL_RAW_DATA, $iv );
}
I have created a FileMaker plugin (acfplugin) that can generate this sample code for a given verbal key.

Decrypting data with Rsa Private Key/Function

I'm just trying to encode/decode data (decode, firstly) with RSA.
I don't care of the type of data (string, bytes, or other lolcat-encoded data), I just looking a very simple function doing the job (cryptographic operations) and I can do the rest.
Here is what I tried :
CryptoPP::InvertibleRSAFunction rsa;
rsa.SetModulus( n_factor );
rsa.SetPrivateExponent(d_private_exponent);
rsa.SetPrime1( rsa_params.at(p1) );
rsa.SetPrime2( rsa_params.at(p2) );
// All above inputs are correct. I don't have public exponent, but it's works in other languages (I comprared all inputs/outputs)
bool key_ok = rsa.Validate(CryptoPP::NullRNG(), 15);
/* Returns false, but doesn't tell me why :/ */
CryptoPP::Integer to_decode( my_data, size_of_my_data );
res = rsa.CalculateInverse(rg,to_decode);
This returns:
EXCEPTION : what(): CryptoMaterial: this object contains invalid values
Returned error code corresponds to "INVALID_DATA_FORMAT", which probably doesn't mean a key problem, but an input problem.
If anyone has some experience with this library, or detects a "noob" mistake (I've my mind in code since 4 hours, so it is possible), any help would be appreciated.
Easier than I thought :
CryptoPP::Integer dec_data = a_exp_b_mod_c( enc_data, d_private_exponent, n_modulus );
So 'd' and 'n' were enought. I really don't understand why it didn't work with crypto features.

RSA private key format

I was trying to find a way to replicate the session-id decoding of mega.co.nz in Ruby using OpenSSL. But, unfortunately, I'm not a cryptography expert.
The problem is I don't understand/recognise the private key format
This is what their code to decompose the private key looks like (https://eu.static.mega.co.nz/crypto.js):
// decompose private key
for (var i = 0; i < 4; i++)
{
var l = ((privk.charCodeAt(0)*256+privk.charCodeAt(1)+7)>>3)+2;
rsa_privk[i] = mpi2b(privk.substr(0,l));
if (typeof rsa_privk[i] == 'number') break;
privk = privk.substr(l);
}
privk itself is 656 bytes long (include eight dashes padding at the end).
After the 'decomposition' they use the decomposed parts to decrypt the session-id (https://eu.static.mega.co.nz/rsa.js):
// Compute m**d mod p*q for RSA private key operations.
function RSAdecrypt(m (encrypted session-id), d (rsa_privk[2]), p (rsa_privk[0]), q (rsa_privk[1]), u (rsa_privk[3]))
How to convert this key so OpenSSL knows how to use it?
OpenSSL supports different key formats, including PEM/X.509 and PKCS8.
The ruby standard library includes an OpenSSL binding.
Using the method provided in this post you can create the key from the exponent and then use for example
key.to_pem()
to convert it to an X.509-formatted string.

Keyboard code mapping in Adobe Flash/Flex for the browser

N.B. Keep in mind the difference between key code and character code. For example, the number 1 (one) and the character ! (bang) both have the same key code but different character codes. Likewise, the number 7 from the row of numbers and the number 7 from the numpad have different key codes but the same character codes.
I'm programming a music rhythm game in Adobe Flex and would like to bind keyboard keys. This isn't a problem, but I certainly would have a problem, say, setting the default keys to A, S, D, and F and telling the user that this is the case.
If you take a look at the documentation for flash.ui.Keyboard, you'll see that there are constants for keyboard keys to key codes. However, these are only available in Adobe AIR and not the browser. This makes sense since not all operating systems and keyboards are alike (or present!), so key codes can vary.
So, how can I assign default keys that have meaning instead of picking key codes and praying?
My only sane thought is to store the character codes for the key bindings and then provide an character code to String mapping so I can tell the user what to press. However, my gut tells me that this will break in subtle or not-so-subtle ways. Like CAPSLOCK.
Thanks.
Update: I am currently using radekg's answer, but I still think that this will break easily. I would feel more comfortable with some persuasion that this is correct.
according to Flash 8 documentation you can simply rely on ASCII codes. Take a look at these topics of the Flash 8 documentation:
http://livedocs.adobe.com/flash/mx2004/main_7_2/wwhelp/wwhimpl/common/html/wwhelp.htm?context=Flash_MX_2004&file=00001113.html
http://livedocs.adobe.com/flash/mx2004/main_7_2/wwhelp/wwhimpl/common/html/wwhelp.htm?context=Flash_MX_2004&file=00001115.html#71579
http://livedocs.adobe.com/flash/mx2004/main_7_2/wwhelp/wwhimpl/common/html/wwhelp.htm?context=Flash_MX_2004&file=00001116.html#71709
http://livedocs.adobe.com/flash/mx2004/main_7_2/wwhelp/wwhimpl/common/html/wwhelp.htm?context=Flash_MX_2004&file=00001114.html#71525
As far as I'm aware of all modern operating systems use standard ASCII. These codes have not changed since Flash 5 era. To compare letters if you don't want to rely on ASCII codes:
private function compareChar(code:Number):void {
if ( "a" == String.fromCharCode( code ).toLowerCase() ) {
trace("A pressed");
} else {
trace("You pressed " + String.fromCharCode(code));
}
}
...
compareChar(65); // 65 is A
compareChar(96); // 96 is numpad 0
However I think you can rely on ASCII codes.
Hope this helps.
Radekg is mostly correct.
Flash has a built in Class flash.ui.KeyLocation with four static properties: LEFT, RIGHT, NUM_PAD, STANDARD. This means that you can have a definitive idea which key has been pressed, even if the keyboard has been remapped. If you this in combination with String.fromCharCode, you should be able to solve this problem rather neatly.
Your final code might look like:
import flash.ui.KeyLocation;
import flash.events.KeyboardEvent;
function handleKeyboardEvent( event:KeyboardEvent )
{
// This will work for a majority of the keys.
var key:String = String.fromCharCode( event.charCode ).toLowerCase();
// Use strict comparison
if( event.keyLocation !== KeyLocation.STANDARD )
{
// The key is a number, then it needs to be re-identified.
if( event.keyLocation == KeyLocation.RIGHT ) key = "r" + key;
if( event.keyLocation == KeyLocation.LEFT ) key = "l" + key;
if( event.keyLocation == KeyLocation.NUM_PAD) key = "n" + key;
}
switch( key )
{
case "r1":
// Do something with the one which is not on the keypad.
break;
case "n.":
// Do something with the number pad's period.
break;
case "n":
// Do something with the letter N.
break;
}
}
I will admit, I am not certain what will happen with String.fromCharCode and control keys (F1, Alt, Ctrl, etc), but the KeyboardEvent does support event.shiftKey, event.ctrlKey, event.altKey

Resources