How to decrypt after Mcrypt deprecation? - php

I have updated my php version to 7.1.
I had functions where i encrypt data using mcrypt.
Now this function is deprecated.
How can i decrypt the data anyway withoud going back to older versions of php.
This is the code i used:
public function encrypt($plaintext) {
$ivSize = mcrypt_get_iv_size(self::CIPHER, self::MODE);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
$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");
}
With Constants:
const CIPHER = MCRYPT_RIJNDAEL_128; // Rijndael-128 is AES
const MODE = MCRYPT_MODE_CBC;
I saw that it was recommended to use OpenSSL. That is what i will use from now on. But how can i decrypt the older data using this method?
Thanks
Edit:
I know i can use OpenSSL as alternative.
Thats what i am doing for the content from now on.
But i need to decrypt my mcrypted code from my old contents.
*Edit request #symcbean
Tried to decrypt with OpenSSL like this:
public function decrypt($ciphertext) {
$ciphertext = base64_decode($ciphertext);
if (!function_exists("openssl_decrypt")) {
throw new Exception("aesDecrypt needs openssl php module.");
}
$key = $this->key;
$method = 'AES-256-CBC';
$ivSize = openssl_cipher_iv_length($method);
$iv = substr($ciphertext,0,$ivSize);
$data = substr($ciphertext,$ivSize);
$clear = openssl_decrypt ($data, $method, $key, 'OPENSSL_RAW_DATA'|'OPENSSL_ZERO_PADDING', $iv);
return $clear;
}

Important thing to note is that mcrypt_encrypt zero-pads input data if it's not a multiple of the blocksize. This leads to ambiguous results if the data itself has trailing zeroes.
openssl_decrypt doesn't remove the zero-padding automatically, so you're left only with the possibility of trimming the trailing nulls.
Here's a trivial example:
$data = "Lorem ipsum";
$key = "1234567890abcdef";
$iv = "1234567890abcdef";
$encrypted = mcrypt_encrypt(
MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
echo bin2hex($encrypted) . "\n";
$decrypted = openssl_decrypt(
$encrypted, "AES-128-CBC", $key,
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
echo var_export($decrypted, true) . "\n";
$result = rtrim($decrypted, "\0");
echo var_export($result, true) . "\n";
Output:
70168f2d5751b3d3bf36b7e6b8ec5843
'Lorem ipsum' . "\0" . '' . "\0" . '' . "\0" . '' . "\0" . '' . "\0" . ''
'Lorem ipsum'

I solved it.
Don't know if its the right way (guess not)
But connected remotely on a server with a lower php version.
Decrypted all the content and encrypted with OpenSSL.
Thanks for the suggestions!

I also had some problems decrypting data encrypted with mcrypt_encrypt with openssl_decrypt. The following small test encrypts a string with mcrypt and openssl (with added zero padding and without) and decrypts all strings with both methods. This example uses ECB mode but you can easily change this to CBC by adding an IV if needed.
// Setup key and test data
$key = hash("sha256", 'test', true);
$data = 'Hello World';
$enc = $dec = [];
// Encrypt with MCRYPT_RIJNDAEL_128 method
$enc['RIJ'] = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB));
// Encrypt with OpenSSL equivalent AES-256
$enc['AES'] = base64_encode(openssl_encrypt($data, 'aes-256-ecb', $key, OPENSSL_RAW_DATA));
// Encrypt with OpenSSL equivalent AES-256 and added zero padding
if (strlen($data) % 8) $data = str_pad($data, strlen($data) + 8 - strlen($data) % 8, "\0");
$enc['AES0'] = base64_encode(openssl_encrypt($data, 'aes-256-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING));
// Decrypt all strings with MCRYPT_RIJNDAEL_128
$dec['mRIJ'] = bin2hex(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($enc['RIJ']), MCRYPT_MODE_ECB));
$dec['mAES'] = bin2hex(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($enc['AES']), MCRYPT_MODE_ECB));
$dec['mAES0'] = bin2hex(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, base64_decode($enc['AES0']), MCRYPT_MODE_ECB));
// Decrypt all strings with OpenSSL equivalent AES-256
$dec['oRIJ'] = bin2hex(openssl_decrypt(base64_decode($enc['RIJ']), 'aes-256-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING));
$dec['oAES'] = bin2hex(openssl_decrypt(base64_decode($enc['AES']), 'aes-256-ecb', $key, OPENSSL_RAW_DATA));
$dec['oAES0'] = bin2hex(openssl_decrypt(base64_decode($enc['AES0']), 'aes-256-ecb', $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING));
// Print results
print_r($enc);
var_dump($dec);
The print_r and var_dump output is the following:
Array
(
[RIJ] => YcvcTwAMLUMBCZXu5XqoEw==
[AES] => +AXMBwkWlgM1YDieGgekSg==
[AES0] => YcvcTwAMLUMBCZXu5XqoEw==
)
array(6) {
["mRIJ"]=>
string(32) "48656c6c6f20576f726c640000000000"
["mAES"]=>
string(32) "48656c6c6f20576f726c640505050505"
["mAES0"]=>
string(32) "48656c6c6f20576f726c640000000000"
["oRIJ"]=>
string(32) "48656c6c6f20576f726c640000000000"
["oAES"]=>
string(22) "48656c6c6f20576f726c64"
["oAES0"]=>
string(32) "48656c6c6f20576f726c640000000000"
}
If you need the same encrypted string with the openssl methods as you had with mcrypt, you'll have add the zero padding manually to the string (AES0 in the example). This way you'll get the exact same encrypted and decrypted strings as before. For some additional information about the zero padding, you should look at Joe's answer here: php: mcrypt_encrypt to openssl_encrypt, and OPENSSL_ZERO_PADDING problems
If you don't want to manually add the zero padding to all new messages, you'll need different flags for decrypting the old mcrypt-encrypted messages and the new messages encrypted with openssl. For the old messages you'll have to use the OPENSSL_ZERO_PADDING flag ($dec['oRIJ'] in the example), whereas you must not use it for the openssl encrypted messages ($dec['oAES'] in the example). In my case I used this approach, because the default behaviour of openssl seems more correct to me as the mcrypt one - if you encrypt a string with 11 bytes you get a string with 11 bytes back after decrypting it. As you can see in the example, this is not the case with mcrypt or with openssl and the added zero padding. In these cases you would have to remove the trailing zeros manually to get the original data back.

Related

Decode encoded data in PHP 7.2

I was using "mcrypt-*" for decoding the response in previous PHP 5.6 version but now in PHP 7.2 version as it is deprecated I am using openSSL method. But it is not working properly hopefully I am missing something.
$value="###lllljG5ZOibDGtlL gcQLAtTQUnCJ/bE2glWsL1WKVPdC22c9GtGe/Npx9Uv9IYaszOAVXB4T9s7Hsss/2XpZ9oisx5M4jeV7RK2S/JrBt2E4GEcDGwuJs6NhkKV8hdOcU tmkJLxO3OJ OgVbqrT6a4v5RE7w eP zvQwZyAR5cYCKUYomou9mL/pvfLbe RrBe5ZnMQmUrD6cwUxEE/inikMvIb4K7HI fVPid N B3iPnIYQna6/v9W5A0kslBj6BBDjVXJabwmCSDVxbArm0GDNseWoQAEa4BMxYitqP6cVTxL5Kri8xbAKCW5/unnYnudkHQjNJWW7LuiwDxsBqwQv8D/R/Ff/joFW6q0 muI16/CfIoFnYAyAJWNlKCX9";
$value = urldecode($value);
$value = str_replace(" ", "+", $value);
$abc = triple_decrypt($value);
print_r($abc);
PHP 5.6 working fine
function triple_decrypt($input){
$key = "thisis87658748639testkey";
$input = base64_decode($input);
$td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_ECB, "");
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
$pwd = trim(mdecrypt_generic($td, $input), "\x00..\x0F");
mcrypt_generic_end($td);
return $pwd;
}
PHP 7.2
function triple_decrypt($input){
$key = "thisis87658748639testkey";
$cipher = "des-ede3";
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
$pwd = openssl_decrypt($input, $cipher, $key, $options=0, $iv);
return $pwd;
}
openssl uses PKCS7-padding and mcrypt Zero-padding [0][1][2]. To decrypt the ciphertext with openssl, openssl's padding must be disabled and mcrypt's Zero-padding bytes must be removed:
function triple_decrypt($input){
$key = "thisis87658748639testkey";
$cipher = "des-ede3";
$decrypted = openssl_decrypt($input, $cipher, $key, $options=OPENSSL_ZERO_PADDING); // Disable openssl's PKCS7-padding
$unpadded = trim($decrypted, "\x00..\x0F"); // Remove mcrypt's Zero-padding bytes
return $unpadded;
}
However, note the following with regard to a reimplementation of encryption and decryption: ECB is an insecure mode [3]. Instead, CBC or even better GCM should be used [4][5]. Instead of Triple-DES the modern and faster todays standard AES is recommended [6]. Zero-padding is unreliable, PKCS7-padding should be applied instead.
Furthermore, the mcrypt code is to some extent inconsistent:
The ECB mode doesn't use an IV (this is also the reason why openssl_cipher_iv_length returns 0 in the openssl code [7]). mcrypt_generic_init ignores the IV in case of the ECB mode [8], so it's not used in the mcrypt code and therefore not needed in the openssl code.
And if a mode would be used that requires an IV, then the following would have to be considered: The IV is always needed for encryption and decryption. Therefore, a random IV is generated (and used) during encryption and then passed on to the recipient together with the ciphertext, where it's used for decryption. Since the IV isn't secret, it's usually prefixed to the ciphertext. The generation of a random IV during decryption therefore makes no sense.
You can do using openssl()
function encryptIt($q) {
$cryptKey = 'YourProjectname'; //any string
$encryptionMethod = "AES-256-CBC";
$secretHash = "25c6c7rr35b9979b151f0205cd13b0vv"; // any hash
//To encrypt
$qEncoded = openssl_encrypt($q, $encryptionMethod, $secretHash);
return $qEncoded;
}
function decryptIt($q) {
$cryptKey = 'YourProjectname'; //any string
$encryptionMethod = "AES-256-CBC";
$secretHash = "25c6c7rr35b9979b151f0205cd13b0vv"; // any hash
//To Decrypt
$qDecoded = openssl_decrypt($q, $encryptionMethod, $secretHash);
return $qDecoded;
}
$encryptedstring = encryptIt('TEST');
echo "<br/>";
echo decryptIt($encryptedstring);

OpenSSL Encryption / Decryption php

I am doing encryption which is working good but with same method I am doing decryption I am getting blank string not getting decryption string. I am using method AES-256-ECB and key is hexadecimal so I pass as
$key = pack('H*','xxxxxxxxxxxx');
Encryption is going correct but decryption is not working. Please help me what I am doing wrong.
function encrypt(string $data, string $key, string $method): string
{
$ivSize = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivSize);
$encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
$encrypted = strtoupper(implode(null, unpack('H*', $encrypted)));
return $encrypted;
}
function decrypt(string $data, string $key, string $method): string
{
$data = pack('H*', $data);
$ivSize = openssl_cipher_iv_length($method);
$iv = $iv = openssl_random_pseudo_bytes($ivSize);
$decrypted = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
return trim($decrypted);
}
Your functions are working perfectly for me with the following code:
$key = pack('H*','aaaaaaaaaaaaa');
$method = 'aes-256-ecb';
$encrypted = encrypt('test string', $key, $method);
$decrypted = decrypt($encrypted, $key.'a', $method);
echo $decrypted; // Output: 'test string'
Since you're getting an empty string for decryption, this means that you've either got the wrong key or cipher text when decrypting. Make sure the key you are using for decryption is exactly the same as the key you're using for encryption, including any manipulations done on it, such as the pack() function you've done here. Even one byte difference and you're not going to be able to decrypt.
Also make sure neither the key nor the cipher text are being truncated when they are stored. If using a database and the column type is too small for what you are trying to store, it will truncate the values.

openssl_encrypt, openssl_decrypt key, iv

According to the documentation of OpenSSL ( https://www.openssl.org/docs/apps/enc.html#OPTIONS ) they expect a hex-digit value for key and iv; does that mean only numbers? or will a md5 hash do? (Because a md5 doesn't seem reversible)
Note i'm mentioning key and iv because $password in the PHP function openssl_encrypt is actually the key.
(Almost) straight from PHP comments ( http://php.net/manual/en/function.openssl-encrypt.php )
function strtohex($x)
{
$s='';
foreach (str_split($x) as $c) $s.=sprintf("%02X",ord($c));
return($s);
}
$source = 'It works !';
$iv = substr( md5( "123sdfsdf4567812345678" ), 0, 16 );
$pass = '1234567812345678';
$method = 'aes-256-cbc';
echo "\niv in hex to use: ".$iv;
echo "\nkey in hex to use: ".strtohex($pass);
echo "\n";
file_put_contents ('./file.encrypted',openssl_encrypt ($source, $method, $pass, true, $iv));
$exec = "openssl enc -".$method." -d -in file.encrypted -nosalt -nopad -K ".strtohex($pass)." -iv ".$iv;
echo 'executing: '.$exec."\n\n";
echo exec ($exec);
echo "\n";
Your first link is about the command-line tools, not the PHP functions. You'd have a hard time throwing binary data in a terminal, hence why the key there has to be hex-encoded.
In PHP however, openssl_encrypt() and openssl_decrypt() expect a raw binary string.
The documentation is also misleading in that it mentions a 'password' instead of 'key'. You've noticed that, but an encryption key is not something that you should just type in via your keyboard and md5()-ing anything is also never the answer for an encryption key.
The key has to be randomly generated via openssl_random_pseudo_bytes() (or at least that's the most convenient way for your case):
$key = openssl_random_pseudo_bytes(32);
(the same goes for IVs as well)
If you need to hex-encode the resulting $key, just pass it to bin2hex(), but the example that you gave is a bit broken ... you're doing double encryption. Encrypting the file contents via PHP is enough, you don't need to deal with the command line.
Please note that my answer is far from the whole story about doing encryption. You should also add authentication, proper padding, think carefully of how to manage & store your keys, etc.
If you want to learn about it, here's a fairly short, but still descriptive blog post that gives the right answers to key points that you should cover: http://timoh6.github.io/2014/06/16/PHP-data-encryption-cheatsheet.html
If what you need is to simply get the job done - use a popular encryption library, don't write your own.
It took me some time to work with the openssl documentation. Finally I had the solution to return encoded and decoded as ASCII text with base64_encode():
//Return encrypted string
public function stringEncrypt ($plainText, $cryptKey = '7R7zX2Urc7qvjhkr') {
$length = 8;
$cstrong = true;
$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;
}

SHA1 the PHP mcrypt_decrypt result

I have 2 encrypt & decrypt functions using PHP mcrypt library.
public function encrypt_string($input, $key) {
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$cipher = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $input, MCRYPT_MODE_CBC, $iv);
return base64_encode($iv . $cipher);
}
public function decrypt_string($input, $key) {
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$ciphertext = base64_decode($input);
$iv = substr($ciphertext, 0, $iv_size);
$cipher = substr($ciphertext, $iv_size);
return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $cipher, MCRYPT_MODE_CBC, $iv);
}
Given that the key is generated by:
$key = pack('H*', 'dfgsdighsdfksdhfosdfasdjldsfsdfgdfkgdl'); // a random key
I can successfully obtain back the input after encryption & decryption.
Here is the code:
$pass = '123456';
echo sha1($pass) . PHP_EOL; // prints 7c4a8d09ca3762af61e59520943dc26494f8941b
$pass_cipher = encrypt_string($pass, $key);
$pass_decrypt = decrypt_string($pass_cipher, $key);
echo $pass_decrypt . PHP_EOL; // prints 123456
echo sha1($pass_decrypt) . PHP_EOL; // prints f41b44dbecccaccfbb4ccf6a7fc4921c03878c6d
However, the SHA1 result is different:
7c4a8d09ca3762af61e59520943dc26494f8941b // before encrypt & decrypt
f41b44dbecccaccfbb4ccf6a7fc4921c03878c6d // after encrypt & decrypt
Why is it different ? What did I miss ?
UPDATE:
The accepted answer is useful. For people who wants additional information, here it is:
echo bin2hex($pass) . PHP_EOL; // prints 313233343536
echo bin2hex($pass_decrypt) . PHP_EOL; // prints 31323334353600000000000000000000
and after trim(), the SHA1 result works as expected, as empty hidden 0 are removed.
Problem is that your decrypt_string returns 16 bytes string, that is filled with 0 bytes at the right side. It's a problem known for about 2 years.
Remove null bytes from the right with line similar to this one:
return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $cipher, MCRYPT_MODE_CBC, $iv), "\0");
Be careful not to encrypt things with null character at the end, as cryptology functions in PHP works as if all strings were null-terminated and are not shy to cut string at first \0 or to return a bit of \0s glued to the end of their output.
in post encrypted data + sign will be replaced with whitespace. thats why decryption was not done .

proper PHP mcrypt encryption methods?

Ok, I have tried to create my own encryption/decryption methods using PHP mcrypt, and when I posted them a while back some called them "trash". They were mentioning things about "Initialization Vectors" and such. Basically, how can I make these cryptography methods better:
function encrypt($key, $data){
$encrypted_data = mcrypt_cbc(MCRYPT_RIJNDAEL_192, $key, $data, MCRYPT_ENCRYPT);
return base64_encode($encrypted_data);
}
function decrypt($key, $encryptedData){
$dec = base64_decode($encryptedData);
$decrypt = mcrypt_cbc(MCRYPT_RIJNDAEL_192, $key, $dec, MCRYPT_DECRYPT);
return trim($decrypt);
}
I want these to work the best they can except I am a duck in a brand new world when it comes to mcrypt, any suggestions are welcome, thanks!
Here is a snippet of the mcrypt functions I use. They use mcrypt_generic and mdecrypt_generic, which should be used according to the PHP manual.
function encrypt($key, $data){
$b = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$enc = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($enc), MCRYPT_DEV_URANDOM);
mcrypt_generic_init($enc, md5($key), $iv);
// PKCS7 Padding from: https://gist.github.com/1077723
$dataPad = $b-(strlen($data)%$b);
$data .= str_repeat(chr($dataPad), $dataPad);
$encrypted_data = mcrypt_generic($enc, $data);
mcrypt_generic_deinit($enc);
mcrypt_module_close($enc);
return array(
'data' => base64_encode($encrypted_data),
'iv' => base64_encode($iv)
);
}
function decrypt($key, $iv, $encryptedData){
$iv = base64_decode($iv);
$enc = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
mcrypt_generic_init($enc, md5($key), $iv);
$encryptedData = base64_decode($encryptedData);
$data = mdecrypt_generic($enc, $encryptedData);
mcrypt_generic_deinit($enc);
mcrypt_module_close($enc);
// PKCS7 Padding from: https://gist.github.com/1077723
$dataPad = ord($data[strlen($data)-1]);
return substr($data, 0, -$dataPad);
}
I don't know much about mcrypt either, so I just kinda hacked these together. I md5 the key so it's always 32 characters (the max key length), and I randomly calculate an "Initialization Vector".
Using PKCS7 Padding is better because you can have strings that end in white space (as trim would remove that), also the encryption is more efficient when the string is a certain length.
I'm using AES 256 (MCRYPT_RIJNDAEL_256) here, but AES 192 (MCRYPT_RIJNDAEL_192) would work too.
Demo: http://ideone.com/WA5Tk
You can create an iv with mcrypt_create_iv(), using the appropriate size for your encryption mode.
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_192, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
Then pass it to mcrypt_cbc() as the optional 5th parameter. The only changes I've made here to your original functions are to pass in $iv:
function encrypt($key, $data, $iv){
$encrypted_data = mcrypt_cbc(MCRYPT_RIJNDAEL_192, $key, $data, MCRYPT_ENCRYPT, $iv);
return base64_encode($encrypted_data);
}
function decrypt($key, $encryptedData, $iv){
$dec = base64_decode($encryptedData);
$decrypt = mcrypt_cbc(MCRYPT_RIJNDAEL_192, $key, $dec, MCRYPT_DECRYPT, $iv);
return trim($decrypt);
}

Categories