I am currently upgrading an existing PHP application from PHP 5.4 to PHP 8.1. I have managed to restore all functionality except for encryption and decryption of data. My application communicates with a third party server, so just running an old PHP version to decrypt using mcrypt and reencrypt using openssl is not possible. I have seen a lot of similar threads, was however unable to find a solution to my issue.
This is the old set of functions (using mcrypt):
static function encrypt($key, $plain, $salt = null)
{
if (is_null($salt))
{
$salt = QuickBooks_Encryption::salt();
}
$plain = serialize(array( $plain, $salt ));
$crypt = mcrypt_module_open('rijndael-256', '', 'ofb', '');
if (false !== stripos(PHP_OS, 'win') and
version_compare(PHP_VERSION, '5.3.0') == -1)
{
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($crypt), MCRYPT_RAND);
}
else
{
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($crypt), MCRYPT_DEV_URANDOM);
}
$ks = mcrypt_enc_get_key_size($crypt);
$key = substr(md5($key), 0, $ks);
mcrypt_generic_init($crypt, $key, $iv);
$encrypted = base64_encode($iv . mcrypt_generic($crypt, $plain));
mcrypt_generic_deinit($crypt);
mcrypt_module_close($crypt);
return $encrypted;
}
static function decrypt($key, $encrypted)
{
$crypt = mcrypt_module_open('rijndael-256', '', 'ofb', '');
$iv_size = mcrypt_enc_get_iv_size($crypt);
$ks = mcrypt_enc_get_key_size($crypt);
$key = substr(md5($key), 0, $ks);
//print('before base64 [' . $encrypted . ']' . '<br />');
$encrypted = base64_decode($encrypted);
//print('given key was: ' . $key);
//print('iv size: ' . $iv_size);
//print('decrypting [' . $encrypted . ']' . '<br />');
mcrypt_generic_init($crypt, $key, substr($encrypted, 0, $iv_size));
$decrypted = trim(mdecrypt_generic($crypt, substr($encrypted, $iv_size)));
mcrypt_generic_deinit($crypt);
mcrypt_module_close($crypt);
//print('decrypted: [[**(' . $salt . ')');
//print_r($decrypted);
//print('**]]');
$tmp = unserialize($decrypted);
$decrypted = current($tmp);
return $decrypted;
}
And this is my best approximation using openssl:
static function new_encrypt($key, $plain, $salt = null)
{
if (is_null($salt))
{
$salt = QuickBooks_Encryption::salt();
}
$plain = serialize(array( $plain, $salt ));
$method = "AES-256-OFB";
$iv_len = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_len);
$key = substr(md5($key), 0, 32);
$encrypted = openssl_encrypt($plain, $method, $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv . $encrypted);
}
static function new_decrypt($key, $encrypted)
{
$iv_size = openssl_cipher_iv_length('aes-256-ofb');
$key = substr(md5($key), 0, 32);
$encrypted = base64_decode($encrypted);
$iv = substr($encrypted, 0, $iv_size);
$encrypted = substr($encrypted, $iv_size);
$decrypted = openssl_decrypt($encrypted, 'aes-256-ofb', $key, OPENSSL_RAW_DATA, $iv);
$tmp = unserialize($decrypted);
$decrypted = current($tmp);
return $decrypted;
}
Both function sets can encrypt and decrypt data; however they are not compatible with each other.
What am I missing here?
First and foremost: Rijndael is not AES.
While AES is descended from/closely related to Rjindael, and may even have certain compatible implementations under very strict interpretations, more often than not they are not compatible at all. The implementations of Rijndael in mcrypt and AES OpenSSL are not compatible. [note: not a cryptographer, some amount of this paragraph might be BS aside from the words 'not compatible']
That said, you can use a library like phpseclib/mcrypt_compat to shim Rijndael and other functionality back into PHP8 and above.
However, since this library code will be running in userspace rather than a compiled extension the performance will likely be noticeably worse. My suggestion would be to use mcrypt_compat to migrate older encryption versions to something OpenSSL-compatible, or at least more current and broadly used/implemented, like AES.
Related
Why can't I decrypt an mcrypt encrypted text with openssl
I have encrypted information in the database and in apps on mobile devices.
So far, these have been encrypted and decrypted on a server with PHP 7.0 and mcrypt.
- The deprecated message is already displayed there.
There will be no mcrypt in the next PHP versions. So I tried to do that with openssl.
Although I also use blowfish with mode CFB at openssl, it doesn't work.
What am I doing wrong?
#
# mcrypt on Server with PHP 7.0
/**
* encrypt with Blowfish and mcrypt
*/
function mcrypt_encrypt($plaintext, $key)
{
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CFB, '');
$ivsize = mcrypt_enc_get_iv_size($td);
$iv = mcrypt_create_iv($ivsize, MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
$crypttext = mcrypt_generic($td, $plaintext);
mcrypt_generic_deinit($td);
return $iv . $crypttext;
}
/**
* decrypt with Blowfish and mcrypt
*/
function mcrypt_decrypt($crypttext, $key)
{
$td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CFB, '');
$ivsize = mcrypt_enc_get_iv_size($td);
$iv = substr($crypttext, 0, $ivsize);
$crypttext = substr($crypttext, $ivsize);
mcrypt_generic_init($td, $key, $iv);
$plaintext = mdecrypt_generic($td, $crypttext);
mcrypt_generic_deinit($td);
return $plaintext;
}
This works on PHP 7.0:
$plaintext = 'Hello World';
$mcrypt_crypttext = mcrypt_encrypt($plaintext,'secret');
$mcrypt_plaintext = mcrypt_decrypt($mcrypt_crypttext,'secret');
# $plaintext == $mcrypt_plaintext;
The new funktions with OpenSSL:
#
# openssl on Server with PHP 7.2
/**
* encrypt with Blowfish and openssl
*/
function openssl_encrypt($plaintext, $key)
{
$ivlen = openssl_cipher_iv_length('bf-cfb');
$iv = openssl_random_pseudo_bytes($ivlen);
$crypttext = openssl_encrypt($plaintext, 'bf-cfb', $key, OPENSSL_RAW_DATA, $iv);
return $iv . $crypttext;
}
/**
* decrypt with Blowfish and openssl
*/
function openssl_decrypt($crypttext, $key)
{
$ivlen = openssl_cipher_iv_length('bf-cfb');
$iv = substr($data, 0, $ivlen);
$crypttext = substr($data, $ivlen);
$plaintext = openssl_decrypt($crypttext, 'bf-cfb', $key, OPENSSL_RAW_DATA, $iv);
return $plaintext;
}
This works also:
$openssl_crypttext = openssl_encrypt($plaintext,'secret');
$openssl_plaintext = openssl_decrypt($openssl_crypttext,'secret');
# $plaintext == $openssl_plaintext;
But this goes wrong - decrypting the mcrypt encrypted text:
$openssl_plaintext = openssl_decrypt($mcrypt_crypttext,'secret');
# $plaintext != $openssl_plaintext
Is there a way to decrypt mcrypted data with PHP7.2?
As you guys probably know, the extension mcrypt will be deprecated on php 7.1.
I use to maintain a "legacy" application that I want to migrate eventually to this version so I ran the tests and verified that I can't get 100% of coverage anymore, since there's a piece of code that use the following code:
$key = 'sA*(DH';
// initialization vector
$iv = md5(md5($key));
$output = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, $iv));
I tried to port this piece of code to openssl_encrypt using this code
$key = md5('sA*(DH');
$iv = md5($key);
echo base64_encode(openssl_encrypt($data, "aes-256-cbc", $key, OPENSSL_RAW_DATA, $iv));
But I have 2 problems with this:
The IV lenght should be 16 chars (and md5 gives me 32), so I get a PHP Warning
The output it's not the same (even if I truncate to 16 chars)
Anyone had similar problems (or know how to fix it?)
BTW: I'm using the dev master version of PHP (supposed to be 7.1.0 alpha 3).
Yet another tested solution taking and returning ANSI text to replace Mcrypt function with the openssl_encrypt() and openssl_decrypt():
//Return encrypted string
public function stringEncrypt ($plainText, $cryptKey = '7R7zX2Urc7qvjhkr') {
$cipher = 'aes-128-cbc';
if (in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt(
$plainText, $cipher, $cryptKey, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $cryptKey, $as_binary=true);
$encodedText = base64_encode( $iv.$hmac.$ciphertext_raw );
}
return $encodedText;
}
//Return decrypted string
public function stringDecrypt ($encodedText, $cryptKey = '7R7zX2Urc7qvjhkr') {
$c = base64_decode($encodedText);
$cipher = 'aes-128-cbc';
if (in_array($cipher, openssl_get_cipher_methods()))
{
$ivlen = openssl_cipher_iv_length($cipher);
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);
$ivlenSha2len = $ivlen+$sha2len;
$ciphertext_raw = substr($c, $ivlen+$sha2len);
$plainText = openssl_decrypt(
$ciphertext_raw, $cipher, $cryptKey, $options=OPENSSL_RAW_DATA, $iv);
}
return $plainText;
}
More read in openssl documentation
You should really get out of the habit of using md5 for anything.
$iv = openssl_random_pseudo_bytes(16);
$key = substr(hash('sha256', 'sA*(DH'), 0, 32)
mcrypt_encrypt and openssl_encrypt will not output the same crypttext given the same plaintext and key.
also, mcrypt is deprecated in PHP 7.1, not removed...so you can update to 7.1 without changing from mcrypt to openssl ... but it is a good idea to remove mcrypt in general.
There are 2 problems :
MCrypt uses zero padding while Openssl uses by default PKCS#7
Openssl needs the input string to be of proper length (multiple of block length)
To solve this problems :
add OPENSSL_ZERO_PADDING flag to openssl_encrypt/openssl_decrypt
if input string length is not multiple of block length then append to the input string zero chars "\0" [aka chr(0)];
That being said this should solve the problem:
// key/iv in ASCII binary data, $str base64
function decrypt_stuff($key, $str, $iv) {
// $plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($str), MCRYPT_MODE_CBC, $iv);
$plaintext_dec = openssl_decrypt(base64_decode($str), "aes-256-cbc", $key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv);
return $plaintext_dec;
}
// key/iv in ascii binary data, $str ascii
function encrypt_stuff($key, $str, $iv) {
// $ciphertext = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_CBC, $iv));
if (($l = (strlen($str) & 15)) > 0) { $str .= str_repeat(chr(0), 16 - $l); }
$ciphertext = base64_encode(openssl_encrypt($str, "aes-256-cbc", $key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv));
return $ciphertext;
}
m using
public function encrypt($plain_str,$key)
{
$str= mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plain_str, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND));
$str = urlencode(base64_encode($str));
return $str ;
}
public function decrypt($cipher_str,$key)
{
$str = urldecode(base64_decode($cipher_str));
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND));
}
on crypting :201433~58~g#fds.com~20140820142427
i get : %2BAihYMLwpwrsmL4lSGGzwFTfonvdCyOb%2BCGEUJ%2F%2BE%2F7ZnvgwFRYFtlazQeSrVjUjyaaGZADK8%2BZyynIGxyt4VQ%3D%3D
on decrypting : %2BAihYMLwpwrsmL4lSGGzwFTfonvdCyOb%2BCGEUJ%2F%2BE%2F7ZnvgwFRYFtlazQeSrVjUjyaaGZADK8%2BZyynIGxyt4VQ%3D%3D
i get :201433~58~g#fds.com~20140820142427 back but
when string is malformed like some character removed
like this : %2BAihYMLwpwrsmL4lSGGzwFTfonvdCyOb%2BCGEUJ%2F%2BE%2F7Z
on decrypting i get : 201433~58~g#fds.com~201408201424O#¿W«Gݽˋ¯ È#'oP´ŸØw\Â⦑
How can i detect this anomoly ?
First of all, I'd like to list some flaws in your code:
Don't use ECB mode.
You are encrypting using MCRYPT_RIJNDAEL_128, but you're getting the IV size for MCRYPT_RIJNDAEL_256. (btw, IV is ignored in ECB mode, which is one of the reasons why not to use it)
You are also using MCRYPT_RAND as your randomness source, which is not secure. You should use MCRYPT_DEV_URANDOM (that is also the new default in PHP 5.6).
You don't have to urlencode() the resulting ciphertext, Base64 encoding is URL-safe.
Now, to answer your question ... this is done via a HMAC. The easiest way to use a HMAC is to prepend the cipher-text with it (which you should do with the IV as well; don't worry, it's not a secret):
public function encrypt($plainText, $encKey, $hmacKey)
{
$ivSize = mcrypt_get_iv_size('rijndael-128', 'ctr');
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
$cipherText = mcrypt_encrypt('rijndael-128', $encKey, $plainText, 'ctr', $iv);
$cipherText = $iv.$cipherText;
$hmac = hash_hmac('sha256', $cipherText, $hmacKey, true);
return base64_encode($hmac.$cipherText);
}
public function decrypt($cipherText, $encKey, $hmacKey)
{
$cipherText = base64_decode($cipherText);
if (strlen($cipherText) <= 32)
{
throw new Exception('Authentication failed!');
}
$recvHmac = substr($cipherText, 0, 32);
$cipherText = substr($cipherText, 32);
$calcHmac = hash_hmac('sha256', $cipherText, $hmacKey, true);
if ( ! hash_equals($recvHmac, $calcHmac))
{
throw new Exception('Authentication failed!');
}
$ivSize = mcrypt_get_iv_size('rijndael-128', 'ctr');
$iv = substr($cipherText, $ivSize);
$cipherText = substr($cipherText, $ivSize);
return mcrypt_decrypt('rijndael-128', $encKey, $cipherText, 'ctr', $iv);
}
Please note that the encryption key and HMAC key are different - they most NOT be the same key. Also, for Rijndael-128, you should create a 128-bit (or 16-byte) random key, it is not something that you can just type in with your keyboard. Here's how to generate one:
$encKey = mcrypt_create_iv(16, MCRYPT_DEV_URANDOM);
i want to decrypt magento data using the data encrypted and config key to show the data as plan test
i tried alotof ways but no one has done with me is there any way
i mean is there any way as php script to do it
and thanks
i used this code i found here but doesn't show anything
<?php
class Encryption
{
const CIPHER = MCRYPT_RIJNDAEL_128; // Rijndael-128 is AES
const MODE = MCRYPT_MODE_CBC;
/* Cryptographic key of length 16, 24 or 32. NOT a password! */
private $key;
public function __construct($key) {
$this->key = $key;
}
public function encrypt($plaintext) {
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_RANDOM);
$ciphertext = mcrypt_encrypt(self::CIPHER, $this->key, $plaintext, self::MODE, $iv);
return base64_encode($iv.$ciphertext);
}
public function decrypt($ciphertext) {
$ciphertext = base64_decode($ciphertext);
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
if (strlen($ciphertext) < $ivSize) {
throw new Exception('Missing initialization vector');
}
$iv = substr($ciphertext, 0, $ivSize);
$ciphertext = substr($ciphertext, $ivSize);
$plaintext = mcrypt_decrypt(self::CIPHER, $this->key, $ciphertext, self::MODE, $iv);
return rtrim($plaintext, "\0");
}
}
In simplest case, when we use all of standard Magento settings:
Encryptor get from model core/encryption,
Key from setting global/crypt/key
Using Mcrypt
With standard cipher MCRYPT_BLOWFISH and mode MCRYPT_MODE_ECB
(all given for Magento 1.8.1)
$encrypted = 'R4VQyYn6JHs=';
$key = '370ee4d319aebb395b982d72190588d2';
$cipher = MCRYPT_BLOWFISH;
$mode = MCRYPT_MODE_ECB;
$handler = mcrypt_module_open($cipher, '', $mode, '');
$initVector = mcrypt_create_iv (mcrypt_enc_get_iv_size($handler), MCRYPT_RAND);
mcrypt_generic_init($handler, $key, $initVector);
var_dump(str_replace("\x0", '', trim(mdecrypt_generic($handler, base64_decode($encrypted)))));
However, I can't see a point in using this, since you can use Magento, and just call
Magento::helper('core')->decrypt($encrypted);
Trying to achieve encrypting and decryption using following strategy, but ending up with random characters mostly.
class Crypt {
public static function encrypt($string, $account) {
// create a random initialization vector to use with CBC encoding
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = pack('H*', $account . $account);
$output = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $iv);
$output = $iv . $output;
$output = base64_encode($output);
$output = urlencode($output);
return $output;
}
public static function decrypt($token, $account) {
$ciphertext_dec = base64_decode($token);
// retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv_dec = substr($ciphertext_dec, 0, $iv_size);
// retrieves the cipher text (everything except the $iv_size in the front)
$ciphertext_dec = substr($ciphertext_dec, $iv_size);
$key = pack('H*', $account . $account);
$token = urldecode($token);
$output = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);
$output = rtrim($output, "");
return $output;
}
}
Can't get exact values back, sometimes it decrypts but I see some garbage values, but mostly just random characters.
$a = \Crypt::encrypt("MyPassword", "1974246e");
echo \Crypt::decrypt($a, "1974246e");
Edits after the discussion
class Crypt {
public static function encrypt($data, $passphrase) {
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); //create a random initialization vector to use with CBC encoding
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$key = pack('H*', $passphrase . $passphrase);
return base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv));
}
public static function decrypt($data, $passphrase) {
$data = base64_decode($data);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); //retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
$iv = substr($data, 0, $iv_size);
$data = substr($data, $iv_size); //retrieves the cipher text (everything except the $iv_size in the front)
$key = pack('H*', $passphrase . $passphrase);
return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv), chr(0));
}
}
Usage:
$pass = "MyPassword*&^*&^(*&^(";
$token = \Crypt::encrypt($pass, "1974246e8e8a479bb0233495e8a3ed12");
$answer = \Crypt::decrypt($token, "1974246e8e8a479bb0233495e8a3ed12");
echo $answer == $pass ? "yes" : "no";
Don't urlencode. Unnecessary.
trim for NULL bytes, not empty strings: rtrim($str, chr(0)); (Instead, you might want to save the source string length in the encrypted result too, so you won't rtrim() too much.)
Why pack('H*', $account) for $key? Also unnecessary.
Rijndael 128 uses 16 byte keys (128 bits), so make sure your key is at least that long:
$key = $account . $account
will do, but it obviously imperfect. (mcrypt will do something like that if it's too short.) If every account had its own passphrase, that would be good. (Even more so in combination with an app secret, but details.)
rtrim() with chr(0) is fine, very probably, because your source string won't have trailing NUL bytes.
I usually use these en/decrypt functions, or alike, but these have a static secret/key, so yours is better.
To send an encrypted token to the client:
$enc_token = Crypt::encrypt($token, $key);
// $enc_token might contain `/` and `+` and `=`
$url = 'page.php?token=' . urlencode($enc_token);