Openssl decryption ( DES ) returns false in PHP - php

I'm trying to decrypt data with DES-ECB encryption, but the response is always false.
When I decrypt the string through https://www.tools4noobs.com/online_tools/decrypt/ the response is correct. This website is using the function "mcrypt_encrypt()" in PHP, but this functionality is not available on my server.
The code that i'm working on should work on PHP 7.1+ version, so the mcrypt_encrypt() isn't available anymore in my system.
$password = 'password'; // Example
$decryptedString = 'ThisShouldBeAnTestToCheckIfTheStringIsCorrectDecryptedThroughDES-ECB';
// Encrypted the string through the online tool.
$encryptedString = 'zOToWEkYOoDnNWZ/sWEgOQQAX97NTZami/3V18yeKmoKiuam3DL0+Pu/LIuvjJ52zbfEx/+6WR4JcCjIBojv0H1eYCDUwY3o';
$opensslDecrypt = openssl_decrypt(base64_decode($encryptedString),'DES-ECB', $password);
var_dump($opensslDecrypt); // Returns false.
I also tried to decrypt without the base64_decode function, but its still returning false.
Anyone have any idea why this isn't decrypting as it should be?

You must precise the $options in you method call:
Just add the following parameter after your password: OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING , ''
<?php
$password = 'password';
$decryptedString =
'ThisShouldBeAnTestToCheckIfTheStringIsCorrectDecryptedThroughDES-ECB';
// Encrypted the string through the online tool.
$encryptedString = 'zOToWEkYOoDnNWZ/sWEgOQQAX97NTZami/3V18yeKmoKiuam3DL0+Pu/LIuvjJ52zbfEx/+6WR4JcCjIBojv0H1eYCDUwY3o';
$opensslDecrypt = openssl_decrypt(base64_decode($encryptedString),'DES-ECB', $password, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING , '');
var_dump(trim($opensslDecrypt));
Output: string(68) "ThisShouldBeAnTestToCheckIfTheStringIsCorrectDecryptedThroughDES-ECB"
For more information about this options:
What does OPENSSL_RAW_DATA do?
$options as (as for 2016) two possible values OPENSSL_RAW_DATA and OPENSSL_ZERO_PADDING. Setting both can be done by OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING. If no OPENSSL_ZERO_PADDING is specify, default pading of PKCS#7 will be done as it's been observe by [openssl at mailismagic dot com]'s coment in openssl_encrypt()
https://www.php.net/manual/en/function.openssl-decrypt.php

Related

AES Encryption producing different results between PHP and Javascript

Hi there StackOverflow community,
After researching for countless of hours, I'm unable to find an explanation as to why my ouputs differ between javascript and my laravel application.
I could use input type hidden to make a post from my web browser, but that would defeat the purpose of having a secure client side processing and I fear that if I don't find the reason as to why this is happening, then decryption (which I plan to do through php) would not work either.
my php code is as follows:
$payload = "this is my plaintext";
$binary_signature = "";
$private_key = openssl_pkey_get_private(file_get_contents(storage_path('privatekey.pem')), 'enc123456789');
openssl_sign($payload, $binary_signature, $private_key, OPENSSL_ALGO_SHA256);
$signature = base64_encode($binary_signature);
$new_payload = $payload."&sign=".$signature; // where my actual plaintext is also used in my javascript code
$key = "thisismykey";
$iv = "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0";
$encryption = openssl_encrypt($new_payload, 'AES-256-CBC', $key,OPENSSL_RAW_DATA, $iv);
dd(base64_encode($encyrption));
which outputs the following:
AEyHK+4DQWYjw8GeVV3mfzUJtk7ylxZINeryAdFptEbDyKOVbmNg8z32J2JgxGpFsQKpXxWaqDLf0IPNIq1jof0rKWhhDpaWzvTd0Tq/zgze7oGtZzEIqtdRDqax3ZvPkzNfuO/O14iW/YTwFkm9FLy9kGIirZDUTuAcOIjXGCgxhrhZHLn+V6SZpW5dYnH8u5rPDCeez2/HkUPI71YjD6hZ0DRjIkiXCyjPYH4fjNykz4yXo8hD+489Zxm8QPq1O1dyjR9JXSdrlYMWdixt6w0vz8EtPC8gZ+bDP/N/UEK07M52VB693zYb5uD1u7WuUUtsOjkr5ocF6QbEW7sjzI4q9yAxqvxRW/bkKqodcVqtglW6YsdJjrTR0EfA/Or/QF3e3QWVM5/2g4rT3ccE17OP6Rp/46yTpW9lOgS6Qiz2hY95GoaxbLfHB/Vb0Es+UppwDu8bd/u2Qax5erBi5ObZu3AjKNpTem45paspsKH3/vc2Jc810XrVQPjnDdZ8VrXvCgPiulywn5Mj28O7uUQ5bay3Zxy3bmHb7ESDEVMKiSEoru3LzDJ7wwPlidJzPcfWtuiMEMsPOv1Y6LaxtlizWM5/zYJFX/RA4d+KBl+Rn6BoPZDcX/6eh3oUoNhy
My JS Code (the plaintext is received through an ajax call which has the same sign method as you see from the php code, the encryption plaintext is of the same value from my php code)
function encrypt(plaintext, secretkey) {
var randomSeed = "";
randomSeed = secretkey;
var key = CryptoJS.enc.Utf8.parse(randomSeed.toUpperCase());
var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
var encrypt = {};
encrypt = CryptoJS.AES.encrypt(plaintext, key, {
mode : CryptoJS.mode.CBC,
iv : iv
});
console.log("encryption is "+encrypt.toString());
return encrypt.toString(encrypt.toString());
}
Returns the correct value which is:
jLmAUr+JyCjbpctU4z6+dlF61jbHRphwTS0iAk3IRiy3jkfCtaCSWdiIO0awuX6G1jAlZroTiAuMl9OW0zj0q4HitndfGFtFUoMMCqZTzvMr6cy1TyG9EFz20T6ByrBnOvGuoVjv3Flufuk2Ghz5in2W2A3T+wF+SPXX/bIAnHtE3uW0bPl2q5tn6KyUI1uoQaYcMZKRPyzAQS7WSSwSOmAcVrRuDANgZQuO+3mh86QAdeFaYqFdZUnxD4c2kkbkGy17SUFfSK8Qjv+8tkTcYXV9QRRdWjGZiQQeyAr3PDKA4SDVzrcMNwJjTaLJiZv0Iau66HGpbf2yvRDLtIOoXQmnhs6NKTZpcSwZ07hHqVvBZmNRq+jqZOGw1s8GRH+Bz4yxSRycTS0DEddhyMoxhZcUc5wt42vDOYIEH2nuw/uu4gjrwpx0rVO1ssoZYRxvBaA6zSC4N04Wdn4JE2/LtXertDLEdLBtmk3c3n4QDU0tK5v31HMY7P7+fdQXU62niVxCNPSt9dpYa82IUrQuigNXgrbphQvZNmmcONi/4pnxJjKcKYpCn/1KhkBVUAhYm6UKJJvMNAo0M+cfsvReImrJx6IzPRdzTTFAQF5kW2NFkV4EIb0DtCF679RtdAhg5ShaP7QhqYL6EgFCs9WnJTACC26TmV20DAqUiuIYULLtjDW4qFOWi/y8D1JOWTar
I don't understand why my PHP encryption is giving the wrong output while my JavaScript encryption is giving the correct output
I'm hoping someone could give me an insight as to what I'm doing wrong from my PHP side. Wha I'm hoping to achieve is that my PHP encryption will output the same result as my JavaScript encryption.
Thank you in advance :)
I have solved my issue.
To those having a similar issue, here's a brief explanation. Crypto JS uses the following:
var key = CryptoJS.enc.Utf8.parse(randomSeed.toUpperCase());
var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
Which translates to a word array if you console log the output. PHP needs to have the similar word array value that cryptoJS produces for it's AES encryption method. To solve this, you have to convert your $key and iv to be in hex format and format your code in php such as:
$key = pack("H*", "4a424d56595753555047553830334d42505a314f414256414c5a565239324659");
$iv = pack("H*", "30303030303030303030303030303030");
Then when you proceed to using openssl_encrypt
$encrypted_data = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
you will get the same output as crpytoJS.
Hope this helps.

How can I solve this OpenSSL "EVP_DecryptFinal_ex:bad decrypt" error in PHP?

Setup:
session_start();
function set_encryption_method() {
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > 3600) {
unset($_SESSION['cipher']);
unset($_SESSION['iv']);
unset($_SESSION['last_activity']);
}
$cipher = 'aes-256-cbc';
$iv = random_bytes(16);
if (in_array($cipher, openssl_get_cipher_methods())) {
if (!isset($_SESSION['cipher'])) {
$_SESSION['cipher'] = $cipher;
}
if (!isset($_SESSION['iv'])) {
$_SESSION['iv'] = $iv;
}
$_SESSION['last_activity'] = time();
} else {
die('Encryption method not supported!');
}
}
set_encryption_method();
Encrypt:
function encrypt_string($key, $string) {
// $key is a constant stored in a database
return rawurlencode(base64_encode(openssl_encrypt($string, $_SESSION['cipher'], $key, 0, $_SESSION['iv'])));
}
Decrypt:
function decrypt_string($key, $encrypted) {
// $key is a constant stored in a database
return openssl_decrypt(rawurldecode(base64_decode($encrypted)), $_SESSION['cipher'], $key, 0, $_SESSION['iv']);
}
When decrypt_string() is called with the appropriate parameters, it throws this error: digital envelope routines evp_decrypt_final_ex: bad decrypt. If I hardcode the iv, then it works correctly.
What am I doing wrong?
The error message is (indirectly) caused by the fact that your are using different IVs for encryption and decryption. From your description it is not clear how that can happen, but let me propose some suggestions that will avoid your issue altogether.
First, with EAS-CBC it is not a good idea to use the same IV + key combination multiple times. You can find some discussion on that in the answers to Is AES in CBC mode secure if a known and/or fixed IV is used?. You did not mention how you are using the different functions, but during 3600 seconds, you are using the same IV + key combinations.
To work around this, you could generate a random IV for every encryption that you do. You could then store the IV together with the encrypted data; the IV is not required or supposed to be secret. The following two functions are modifications of yours that do exactly that, by concatenating the two after encryption and splitting the two before decryption:
function encrypt_string($key, $string) {
// $key is a constant stored in a database
$iv = random_bytes(16);
$ciphtxt = openssl_encrypt($string, $_SESSION['cipher'], $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv.$ciphtxt);
}
function decrypt_string($key, $encrypted) {
// $key is a constant stored in a database
$combo = base64_decode($encrypted);
$iv = substr($combo, 0, 16);
$ciphtxt= substr($combo, 16);
return openssl_decrypt($ciphtxt, $_SESSION['cipher'], $key, OPENSSL_RAW_DATA, $iv);
}
Note the use of the flag OPENSSL_RAW_DATA. As the documentation for openssl_encrypt mentions (not too clearly), the result will be base64-ed if you do not give that flag. You were doing the base64-ing yourself so I added the flag. This also makes it a little easier to handle the (de-)concatenation of the IV and the ciphertext.
A few words about the occurrence of that bad decrypt error: that is emitted when the padding bytes are different than expected. Since you were using an incorrect IV for decryption, the AES decryption did not result in the original data, with a high probability that the padding bytes therefore did not have the right value.
First point to address: Why are you using URL encode/decode?
Those are for passing parameters through browsers; and the browser generally does the decoding for you so as a base rule - if you've written "urldecode" in your PHP, you've probably gone wrong as you're possibly decoding something already decoded. (Or are not using in the right place, or using when unnecessary)
However, Base64 is (or should be) url-safe, so I'd suggest knocking out the urlencode/decode and see what happens.
Second point to address: you need to undo the urlencode/base64 conversion in the reverse order you set.
So rawurlencode(base64_encode(openssl_encrypt( reversed is openssl_decrypt(base64_encode(rawurldecode (you have the base64 and url decode flipped). But knocking out the urlencode/decode entirely (unless you're adamant you need it) will rectify this too.

php two-way encryption for passing variables through URL

OK... to the point...
I have a e-mailshot program that sends out thousands of emails - each with str_replace() merge fields (fields that get replaced by a row value in a recordset).
One of the important parts is my ability to track those mails on open so I include a single server-side generated pixel...
<img src="http://...trace.php?email=<<<EMAIL>>>" alt="" height="1" width="1">
The str_replace() replaces <<<EMAIL>>> with a unique real email address.
The trace.php file reads the $_GET['email'] and either logs it or sends mail confirmation.
My issue is security :)
I want to use two-way encryption so that the $_GET variable sent in the URL is an encrypted email. The trace.php file then needs to decrypt it.
As it's being sent in a URL, It has to be in ASCII format otherwise it will corrupt before decrypting.
I can't use openssl_encrypt() & openssl_decrypt() and I'm having to work with php 5.2.0 (don't hurl abuse at me!).
Any help would be greatly appreciated!!
While many of the comments you have received offer other valid ways of solving the problem e.g. a table of email addresses with primary keys, I am of the position that the best way to solve the problem is the way you originally intended: including the email address encrypted in the query URL.
I feel that this way is better because:
Computing the email address does not require database access. Database bottle-necking is generally the biggest offender for high-latency requests.
Encryption means that the same email address will produce a different IV/ciphertext pair each time you encrypt it. Thus, if you send multiple emails at different times (say, for two different marketing campaigns), the URL will be different each time. This may not have an effect, but it does provide a security advantage in that an attacker can't "pretend" that an email has been opened simply by visiting a URL.
The issue is that for this way to be better, you have to do it well. I've included an excerpt in PHP from this repository below. If you can't use openssl_* then upgrade your PHP version. Do not, ever, use the mcrypt_ functions. They are deprecated for a reason. You may need to hex encode instead of base64 encode the email addresses as is done in the example below.
<?php
define("ALGORITHM_NAME", "aes-128-gcm");
define("ALGORITHM_NONCE_SIZE", 12);
define("ALGORITHM_TAG_SIZE", 16);
define("ALGORITHM_KEY_SIZE", 16);
define("PBKDF2_NAME", "sha256");
define("PBKDF2_SALT_SIZE", 16);
define("PBKDF2_ITERATIONS", 32767);
function encryptString($plaintext, $password) {
// Generate a 128-bit salt using a CSPRNG.
$salt = random_bytes(PBKDF2_SALT_SIZE);
// Derive a key.
$key = hash_pbkdf2(PBKDF2_NAME, $password, $salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, true);
// Encrypt and prepend salt and return as base64 string.
return base64_encode($salt . encrypt($plaintext, $key));
}
function decryptString($base64CiphertextAndNonceAndSalt, $password) {
// Decode the base64.
$ciphertextAndNonceAndSalt = base64_decode($base64CiphertextAndNonceAndSalt);
// Retrieve the salt and ciphertextAndNonce.
$salt = substr($ciphertextAndNonceAndSalt, 0, PBKDF2_SALT_SIZE);
$ciphertextAndNonce = substr($ciphertextAndNonceAndSalt, PBKDF2_SALT_SIZE);
// Derive the key.
$key = hash_pbkdf2(PBKDF2_NAME, $password, $salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, true);
// Decrypt and return result.
return decrypt($ciphertextAndNonce, $key);
}
function encrypt($plaintext, $key) {
// Generate a 96-bit nonce using a CSPRNG.
$nonce = random_bytes(ALGORITHM_NONCE_SIZE);
// Encrypt and prepend nonce.
$ciphertext = openssl_encrypt($plaintext, ALGORITHM_NAME, $key, OPENSSL_RAW_DATA, $nonce, $tag);
return $nonce . $ciphertext . $tag;
}
function decrypt($ciphertextAndNonce, $key) {
// Retrieve the nonce and ciphertext.
$nonce = substr($ciphertextAndNonce, 0, ALGORITHM_NONCE_SIZE);
$ciphertext = substr($ciphertextAndNonce, ALGORITHM_NONCE_SIZE, strlen($ciphertextAndNonce) - ALGORITHM_NONCE_SIZE - ALGORITHM_TAG_SIZE);
$tag = substr($ciphertextAndNonce, strlen($ciphertextAndNonce) - ALGORITHM_TAG_SIZE);
// Decrypt and return result.
return openssl_decrypt($ciphertext, ALGORITHM_NAME, $key, OPENSSL_RAW_DATA, $nonce, $tag);
}
?>

How to decrypt node AES encryption in php?

I have code written in node.js:
var encodeKey = crypto.createHash('sha256').update(pass, 'utf-8').digest();
var cipher = crypto.createDecipheriv('aes-256-cbc', encodeKey, iv);
console.log(cipher.update(text, 'hex', 'utf8') + cipher.final('utf8'));
i have text, pass and iv vars. When i try translate this code into PHP, i have different results. This is PHP code:
$encodeKey = hash('sha256', $pass);
$decrypted = openssl_decrypt($text, 'aes-256-cbc', $encodeKey, 0, $iv);
Where is the bug? Thanks a lot for any suggestion!
Your JS code...
var encodeKey = crypto.createHash('sha256').update(pass, 'utf-8').digest();
...and your PHP code...
$encodeKey = hash('sha256', $pass);
Are not the same. They're producing different keys, which will result in different encryption.
Try this instead:
$encodeKey = hash('sha256', $pass, true);
However, this is only part of the problem.
AES-CBC is vulnerable to chosen-ciphertext attacks.
IVs should be randomly generated per message, not held constant. (It isn't clear from your code if this is static or not.)
Recommendation: Switch to Sodium-Plus (JS) and ext/sodium (PHP).
If you upgrade PHP to 7.2 or newer, you should get libsodium for free. Otherwise, sodium_compat will do the trick.

mcrypt_ecb not $decrypted correctly when I use file_get_contents PHP

I have code which first I encrypted using mcrypt_ecb and hten then i send the value to another page using file_get_contents. but when I echo receive data, it only print half of it. I have attached my code and result below:
Client
$mac="B8-AC-6F-2D-5C-23";
// encrpt the max address
$key_value = pack('H*', "bcb04b7e103a0c");
$plain_text = $mac;
$encrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $plain_text, MCRYPT_ENCRYPT);
echo ("<p><b> Text after encryption : </b>");
echo ( $encrypted_text );
// send encrypted mac address to bridge for verification
$response = file_get_contents('http://localhost/scale/check.php?mac='.$encrypted_text);
print_r( $response );
Host
if (isset($_GET['mac']) && $_GET['mac']) {
$mac = $_GET['mac'];
echo "<br/>";
echo $mac;
//decrypted recieve data
$key_value = pack('H*', "bcb04b7e103a0c");
/* #var $encrypted_text type */
$encrypted_text = $mac;
$decrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $encrypted_text, MCRYPT_DECRYPT);
echo ("<p><b> Text after decryption : </b>");
echo ( $decrypted_text );
}
Result:
B8-AC-6F-2D-5C-23
Text after encryption : 5"ÆfÛkã–]» º"÷5Ù(Ÿ©U
_5"ÆfÛk_ã–]»
Text after decryption : 9Bþ‚î10tçæÇ|¤
The output of mcrypt_ecb is binary data, its bytes can contain arbitrary values. If you want to transfer it as an url parameter you have to encode it first. The general problem is that the url parameter parsing assumes a certain structure of the url. For example if you write http://localhost/scale/check.php?mac=abc123&foo=bar then PHP will see two parameters, the first one being mac with value abc123, the second being foo with value bar. But in the way you just concatenate the ciphertext to the url you could end up with the same url even if you really meant to have a parameter mac with the value abc123&foo=bar. To prevent such confusions all values have to be encoded so that certain characters do not appear. In your example the space seems to be problematic.
You can find some background on url encoding here: https://en.wikipedia.org/wiki/Percent-encoding .
By the way: It is generally appreciated if you only post a minimal example of your problem. Given that you have issues with the transmission of your data you could remove all the encryption code. But as I've seen it now, I can't help but notice that the algorithm DES and the block cipher mode ECB is generally a very insecure choice. If you want to protect your data during transmission you can avoid all the usual issues with home grown crypto schemes by using an https connection (notice the "s" for "secure", which forces your http client to connect to the server using TLS).

Categories