Decrypt Crypto-js encrypted text with key with PHP - javascript

I'm using Crypto-js for encrypting password with a key and send it to server. I want to decrypt it in server using PHP. How this can be done?
JS:
let encKey = "Secret Passphrase";
let text = "123";
let iv = CryptoJS.enc.Hex.parse("FgLFXEr1MZl2mEnk");
var encryptedText = CryptoJS.AES.encrypt(text, encKey, { iv: iv }).toString();
Encrypted text:
U2FsdGVkX1+EaW3J1GE1k/EU5h6C+nxBH364Xhez+b0=
PHP:
<?php
$strg = "U2FsdGVkX1+EaW3J1GE1k/EU5h6C+nxBH364Xhez+b0=";
$encryptedstrings = base64_decode($strg);
$encryptionMethod = 'aes-256-cbc';
$key = "Secret Passphrase";
$iv = "FgLFXEr1MZl2mEnk";
$rawText = openssl_decrypt($encryptedstrings, $encryptionMethod, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING , $iv);
var_dump($rawText);
Result:
string(32) "����>���s��ȡ�V?E��M���I"
I'm getting weird results here.

The following solution is not from my side but from #Artjom B., so all credits go to him. You will find the source here: https://stackoverflow.com/a/27678978/8166854.
To your problem: you run the CryptoJs encryption with a passphrase and not with a key. According to the docs (https://cryptojs.gitbook.io/docs/#the-cipher-algorithms) section cipher algorithms the (internal AES) key is derived from the passphrase with an outdated and unsecure function that should no longer be in use.
Artjom B. was able to make this key derivation available on PHP. As a side note: it is not necessary to present an
initialization vector (IV) to the encryption function as the IV is as well derived from the passphrase, so I'm leaving it
out in the following code.
This is the result on PHP-side:
solution for https://stackoverflow.com/questions/65234428/decrypt-crypto-js-encrypted-text-with-key-with-php
string(3) "123"
decryptedtext: 123
This is the code, please obey the warning:
This code is provided for achieve compatibility between different programming languages. It is not necessarily fully secure. Its security depends on the complexity and length of the password, because of only one iteration and the use of MD5. I would recommend to use at least a 20 character password with alphanumeric characters which is ideally randomly generated.
<?php
/*
source: https://stackoverflow.com/a/27678978/8166854 author: Artjom B.
Security notice: This code is provided for achieve compatibility between different programming languages.
It is not necessarily fully secure. Its security depends on the complexity and length of the password,
because of only one iteration and the use of MD5. I would recommend to use at least a 20 character password
with alphanumeric characters which is ideally randomly generated.
*/
function evpKDF($password, $salt, $keySize = 8, $ivSize = 4, $iterations = 1, $hashAlgorithm = "md5") {
$targetKeySize = $keySize + $ivSize;
$derivedBytes = "";
$numberOfDerivedWords = 0;
$block = NULL;
$hasher = hash_init($hashAlgorithm);
while ($numberOfDerivedWords < $targetKeySize) {
if ($block != NULL) {
hash_update($hasher, $block);
}
hash_update($hasher, $password);
hash_update($hasher, $salt);
$block = hash_final($hasher, TRUE);
$hasher = hash_init($hashAlgorithm);
// Iterations
for ($i = 1; $i < $iterations; $i++) {
hash_update($hasher, $block);
$block = hash_final($hasher, TRUE);
$hasher = hash_init($hashAlgorithm);
}
$derivedBytes .= substr($block, 0, min(strlen($block), ($targetKeySize - $numberOfDerivedWords) * 4));
$numberOfDerivedWords += strlen($block)/4;
}
return array(
"key" => substr($derivedBytes, 0, $keySize * 4),
"iv" => substr($derivedBytes, $keySize * 4, $ivSize * 4)
);
}
function decrypt($ciphertext, $password) {
$ciphertext = base64_decode($ciphertext);
if (substr($ciphertext, 0, 8) != "Salted__") {
return false;
}
$salt = substr($ciphertext, 8, 8);
$keyAndIV = evpKDF($password, $salt);
$decryptPassword = openssl_decrypt(
substr($ciphertext, 16),
"aes-256-cbc",
$keyAndIV["key"],
OPENSSL_RAW_DATA, // base64 was already decoded
$keyAndIV["iv"]);
return $decryptPassword;
}
echo 'solution for https://stackoverflow.com/questions/65234428/decrypt-crypto-js-encrypted-text-with-key-with-php' . PHP_EOL;
$key = "Secret Passphrase";
$strg = "U2FsdGVkX1+EaW3J1GE1k/EU5h6C+nxBH364Xhez+b0=";
$rawText = decrypt($strg, $key);
var_dump($rawText);
echo 'decryptedtext: ' . $rawText . PHP_EOL;
?>

Related

How can I securely generate the same private key in JS and PHP?

I need to create an internet-less password reset feature. My hardware uses a web (JS/HTML) interface and a PHP back-end. I've seen this in the wild before: the user gets a short key which they read to the IT person, and the IT person generates a short private response key. The user then uses this key and is able to get to a password change screen.
Yes, I have a "security questions" feature already. This is going to be for last-ditch recovery, like if your last Admin user dies or something. Also, it cannot be time-sensitive like standard keyfob RSA. Some of these units are literally on top of mountains, so a person might have a considerable lag time between generating the public key, receiving the private one, and setting it on their device.
I've considered just taking something like the unit's serial and the date and running them through some sort of encryption, but I wonder if you guys have some simpler way? Hell, I could encrypt it the same way I do the passwords, but the strings that generates can be... unwieldy. Ideally this would be maybe a 10-12 character string.
I found a pretty slick way to do it. I took a set of data ($serial, hard-coded $seed, $date, and $type for account type) md5'd it, and encrypted that with openssl using an encrypt function I found on another answer, then did sort of the first half of a checksum to condense the long string to something 1/3 the length. You just run genkey($serial,$seed,$date,$type) to get a nice short string, such as "URMYVAFYNE". The person on the other end does the same thing, then compares the strings to see if they match. Then they generate a second string using the first one as the $seed, and the client enters that to have it verified... you get the idea.
function genkey($serial,$seed,$date,$type) {
$fullstring = $serial.$seed.$date.$type;
$md5 = md5($fullstring);
$encrypted = encrypt_decrypt('encrypt',$md5);
return condense(condense($encrypted));
}
function encrypt_decrypt($action, $string)
{
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = '[REDACTED]'; //32 byte
$secret_iv = '[REDACTED]'; //16 byte
// hash
$key = hash('sha256', $secret_key);
// iv - encrypt method AES-256-CBC expects 16 bytes
$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;
}
function condense($str) {
$arr = str_split($str, 3);
$retstr = '';
for($i=0;$i<count($arr);$i++) {
$sum=WordSum($arr[$i]);
$letternum = $sum%26 + 65; //ascii code for capital letter
$letter = chr($letternum);
$retstr .= $letter;
}
return $retstr;
}

How to decrypt AES256 data which was encrypted on PHP and get value in Javascript?

I have encrypted some value using aes-256-cbc mode on PHP like this:
public function encrypt(string $data): string
{
$iv = $this->getIv();
$encryptedRaw = openssl_encrypt(
$data,
$this->cryptMethod, //aes-256-cbc
$this->key,
OPENSSL_RAW_DATA,
$iv
);
$hash = hash_hmac('sha256', $encryptedRaw, $this->key, true);
return base64_encode( $iv . $hash . $encryptedRaw );
}
Then I tried to decrypt it on PHP and it works fine:
public function decrypt(string $data): string
{
$decoded = base64_decode($data);
$ivLength = openssl_cipher_iv_length($this->cryptMethod);
$iv = substr($decoded, 0, $ivLength);
$hmac = substr($decoded, $ivLength, $shaLength = 32);
$decryptedRaw = substr($decoded, $ivLength + $shaLength);
$originalData = openssl_decrypt(
$decryptedRaw,
$this->cryptMethod,
$this->key,
OPENSSL_RAW_DATA,
$iv
);
So I'm new to JavaScript and I don't know how to realize the same decrypt method as on php.
Example of encrypted string and it's key:
encrypted string lUIMFpajICh/e44Mwkr0q9xdyJh5Q8zEJHi8etax5BRl78Vsyh+wDknmBga1L8p8SDZA6WKz1CvAAREFGreRAQ== secret key - 9SJ6O6IwmItSRICbXgdJ
Example what I found returns empty string:
const decodedString = base64.decode(
`lUIMFpajICh/e44Mwkr0q9xdyJh5Q8zEJHi8etax5BRl78Vsyh+wDknmBga1L8p8SDZA6WKz1CvAAREFGreRAQ==`
);
const CryptoJS = require("crypto-js");
var key = CryptoJS.enc.Latin1.parse("9SJ6O6IwmItSRICbXgdJ");
var iv = CryptoJS.enc.Latin1.parse(decodedString.slice(0, 16));
var ctx = CryptoJS.enc.Base64.parse(
"lUIMFpajICh/e44Mwkr0q9xdyJh5Q8zEJHi8etax5BRl78Vsyh+wDknmBga1L8p8SDZA6WKz1CvAAREFGreRAQ=="
);
var enc = CryptoJS.lib.CipherParams.create({ ciphertext: ctx });
console.log(
CryptoJS.AES.decrypt(enc, key, { iv: iv }).toString(CryptoJS.enc.Utf8)
);
}
What I did wrong?
The key used in the PHP code is only 20 bytes in size and thus too small for AES-256 (AES-256 requires a 32 bytes key). PHP/OpenSSL implicitly pads the key with 0x00 values to the required key length. In the CryptoJS code, this must be done explicitly.
Furthermore, in the CryptoJS code, IV (the first 16 bytes), HMAC (the following 32 bytes) and ciphertext (the rest) are not separated correctly.
Also, the authentication is missing. To do this, the HMAC for the ciphertext must be determined using the key and compared with the HMAC sent. Decryption only takes place if authentication is successful.
If all of this is taken into account, the posted code can be fixed e.g. as follows:
var key = CryptoJS.enc.Utf8.parse("9SJ6O6IwmItSRICbXgdJ".padEnd(32, "\0")); // pad key
var ivMacCiphertext = CryptoJS.enc.Base64.parse("lUIMFpajICh/e44Mwkr0q9xdyJh5Q8zEJHi8etax5BRl78Vsyh+wDknmBga1L8p8SDZA6WKz1CvAAREFGreRAQ==")
var iv = CryptoJS.lib.WordArray.create(ivMacCiphertext.words.slice(0, 4)); // get IV
var hmac = CryptoJS.lib.WordArray.create(ivMacCiphertext.words.slice(4, 4 + 8)); // get HMAC
var ct = CryptoJS.lib.WordArray.create(ivMacCiphertext.words.slice(4 + 8)); // get Ciphertext
var hmacCalc = CryptoJS.HmacSHA256(ct, key);
if (hmac.toString() === hmacCalc.toString()) { // authenticate
var dt = CryptoJS.AES.decrypt({ciphertext: ct}, key, { iv: iv }).toString(CryptoJS.enc.Utf8); // decrypt
console.log(dt);
} else {
console.log("Decryption failed");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
A few thoughts for you:
Check that your encoding/decoding is working properly. For each stage
of the process, endode/decode, then console log the output and
compare input to output, and also between PHP and javascript.
CBC mode uses padding to fill out the blocks. Check that both stacks
are using the same padding type.
Rather than using CBC and a separate HMAC, how about jumping to AEAD (like AES
GCM) which avoids the padding issue, and also incorporates the MAC
into the encryption, so is a more simple interface?

PHP decryption from JS breaks on longer values

When I'm trying to encrypt and decrypt a short word with several characters the following code does work. However if I try that with: gmdv4hwHN7SrHwEhX0Sb6dskWkVezAUlmjTvHOV6QSAySI8pOrOsPrQoYVQpWP7j It's like there's a max amount of characters. How do I resolve this issue?
JS
var CryptoJS = require("crypto-js")
var message = 'gmdv4hwHN7SrHwEhX0Sb6dskWkVezAUlmjTvHOV6QSAySI8pOrOsPrQoYVQpWP7j';
var key = '59b6ab45d379b89d794c87b74a511';
var iv = '0aaff094b6bc297a';
var encrypted = CryptoJS.AES.encrypt(
message,
CryptoJS.enc.Hex.parse(key),
{ iv: CryptoJS.enc.Hex.parse(iv) }
).toString()
Output: rQAT7R3TJUU+iLIzY+MNpoer7/br60oQrxo2O7BmI0b/O668bT7L5/cJUQbAFFDcwX4+8g3kjem6pCMGU7u9srVM7yauPmw8lcW9IiWSrbg=
PHP
<?php
$ciphertext = "rQAT7R3TJUU+iLIzY+MNpoer7/br60oQrxo2O7BmI0b/O668bT7L5/cJUQbAFFDcwX4+8g3kjem6pCMGU7u9srVM7yauPmw8lcW9IiWSrbg=";
$key = '59b6ab45d379b89d794c87b74a511';
$iv = '0aaff094b6bc297a';
var_dump (\openssl_decrypt(
base64_decode($ciphertext),
'aes-256-cbc',
$key,
OPENSSL_RAW_DATA,
$iv
));
Output: bool(false)

CryptoJS extra parameter in AES Encrypt. How to replicate with PHP? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I am trying to replicate the following CryptoJS 3.1.2 code with PHP 5.4.4 :
var plaintext = 'test';
var key = CryptoJS.enc.Utf8.parse('9rIY8vV8DdOKx3m6JocjEaObhhYc2NfY');
var iv = CryptoJS.enc.Utf8.parse('r0dkbnVQhklNeUGA');
var encrypted = CryptoJS.AES.encrypt(plaintext, 'euhe68vjdr1aX4F091c7aCggSMBf0A7M', key,{iv: iv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7});
I have found several questions dealing with that problem, but none of them had that extra parameter in encrypt() between plaintext and key.
The CryptoJS wiki does not mention this parameter at all.
And yet, it works. I can decrypt with this :
var decrypted = CryptoJS.AES.decrypt(encrypted, 'euhe68vjdr1aX4F091c7aCggSMBf0A7M', key, {iv: iv});
If I omit 'euhe68vjdr1aX4F091c7aCggSMBf0A7M' in the decrypt() call, it won't work.
So that parameter really does something. But what? Is is a hash, a salt?
Does anybody know how I can replicate this specific encryption process with PHP?
I can not modify this JS code in any way.
The code is on a login page and is used to encrypt credentials before they are sent to the server.
I am trying to use PHP-cURL to submit credentials to that server from the command line, which is why I need to reproduce this encryption process.
Update: thanks to Jim's answer, I now have the proper JavaScript, now I need help with replicating the code in PHP
Here is the JS :
var plaintext = 'test';
var key = 'euhe68vjdr1aX4F091c7aCggSMBf0A7M';
var iv = CryptoJS.enc.Utf8.parse('r0dkbnVQhklNeUGA');
var encrypted = CryptoJS.AES.encrypt(plaintext, key, key,{iv: iv,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7})
encrypted equals U2FsdGVkX1+ZujoXKqDHcO+4N1QO+Nv1KHUMFjZos1s=
Here is the decryption:
var decrypted = CryptoJS.AES.decrypt(encrypted, key);
decrypted equals test
I have tried many different ways to replicate the encryption with PHP, but none of them gives a a string I can decode with the above JS.
For instance, using this AES_Encryption class, I tried the following code :
$key = 'euhe68vjdr1aX4F091c7aCggSMBf0A7M';
$iv = 'r0dkbnVQhklNeUGA';
$message = 'test';
$AES = new AES_Encryption($key, $iv, PKCS7);
$encrypted = $AES->encrypt($message);
$decrypted = $AES->decrypt($encrypted);
$base64_encrypted = base64_encode('Salted__'.$encrypted);
I end up with U2FsdGVkX18eEv+TnigBEKGJL8t/V1Hm instead of U2FsdGVkX1+ZujoXKqDHcO+4N1QO+Nv1KHUMFjZos1s=
Note that both strings start the same way, thanks to the 'Salted__' prefix I added (since CryptoJS seems to do the same thing).
I tried similar code with phpseclib, openssl_encrypt and mcrypt. No luck.
Any hint would be appreciated.
**Update: FIXED **
This PHP code is a perfect match for the CryptoJS code above.
function ssl_encrypt($pass, $data)
{
// Set a random salt
$salt = substr(md5(mt_rand(), true), 8);
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$pad = $block - (strlen($data) % $block);
$data = $data . str_repeat(chr($pad), $pad);
// Setup encryption parameters
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, "", MCRYPT_MODE_CBC, "");
$key_len = mcrypt_enc_get_key_size($td);
$iv_len = mcrypt_enc_get_iv_size($td);
$total_len = $key_len + $iv_len;
$salted = '';
$dx = '';
// Salt the key and iv
while (strlen($salted) < $total_len) {
$dx = md5($dx.$pass.$salt, true);
$salted .= $dx;
}
$key = substr($salted,0,$key_len);
$iv = substr($salted,$key_len,$iv_len);
mcrypt_generic_init($td, $key, $iv);
$encrypted_data = mcrypt_generic($td, $data);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
//return chunk_split(base64_encode('Salted__' . $salt . $encrypted_data),32,"\r\n");
return base64_encode('Salted__' . $salt . $encrypted_data);
}
function ssl_decrypt($password, $edata)
{
$data = base64_decode($edata);
print "Data: " . $data . "\n";
$salt = substr($data, 8, 8);
print "Salt (Base64): " . base64_encode($salt) . "\n";
$ct = substr($data, 16);
print "Content (Base64): " . base64_encode($ct) . "\n";
$rounds = 3;
$data00 = $password.$salt;
print "Data00 (Base64): " . base64_encode($data00) . "\n";
$md5_hash = array();
$md5_hash[0] = md5($data00, true);
$result = $md5_hash[0];
print "MD5-Hash[0] (Base64): " . base64_encode($result) . "\n";
for ($i = 1; $i < $rounds; $i++) {
$md5_hash[$i] = md5($md5_hash[$i - 1].$data00, true);
$result .= $md5_hash[$i];
print "Result (Base64): " . base64_encode($result) . "\n";
}
$key = substr($result, 0, 32);
print "Key (Base64): " . base64_encode($key) . "\n";
$iv = substr($result, 32, 16);
print "IV (Base64): " . base64_encode($iv) . "\n";
print "Decrypted: " . openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv) . "\n";
}
$encryptedString = ssl_encrypt('euhe68vjdr1aX4F091c7aCggSMBf0A7M', 'test');
$decryptedString = ssl_decrypt('euhe68vjdr1aX4F091c7aCggSMBf0A7M', $encryptedString);
I can't remember where I found it. Sorry.
This is a JavaScript thing, not a CryptoJS thing. Try this:
var decrypted = CryptoJS.AES.decrypt(encrypted, 'euhe68vjdr1aX4F091c7aCggSMBf0A7M', {iv: iv});
or this:
key = 'euhe68vjdr1aX4F091c7aCggSMBf0A7M';
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {iv: iv});
You'll see that what you think is the key, isn't the key at all. It's being grouped with the optional parameters at the end, and lost.
'euhe68vjdr1aX4F091c7aCggSMBf0A7M' is your key, or is what your key is derived from.

php mcrypt to javascript aes integration

I am trying to use javascript to encode data with AES-256-CBC and php mcrypt libraries to decode, and vise versa.
I am aware of the problematic nature of javascript and the fact that anyone sees the key, but I am using javascript a scripting tool for non-web environment - so not worried about it.
I found pidder https://sourceforge.net/projects/pidcrypt/
and encrypted some data with the demo page, then tried to decrypt it via php, but something is wrong and I can't seem to find what... I am using the same key with both ends, a 32 byte string
any pointers will be appreciated
~~~
$encrypted = "string after pidder encryption";
$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_256,'',MCRYPT_MODE_CBC,'');
$iv_size = mcrypt_enc_get_iv_size($cipher);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
mcrypt_generic_init($cipher, $key, $iv);
$encrypted = base64_decode($encrypted);
echo "after b64decode: " . $encrypted . "\n\n\n";
$encrypted = mdecrypt_generic($cipher, $encrypted);
echo "decrypt:" . $encrypted;
~~~
Try MCRYPT_RIJNDAEL_128 with a 32-byte key for AES-256.
AES is a 128-bit block cipher that supports 128-, 192-, and 256-bit keys. Rijndael-256 is a 256-bit block cipher and AES. AES is a 128-bit block specification for Rijndael.
Pidder uses key derivation function to get the key from password (it should be HMAC-SHA1, i guess), but you seems to use plain password as a key.
Javascript Mcrypt plays well with PHP mcrypt. You could use that instead of pidder.
Your code is sequential, honestly, I dont tried to fix, but I have a function that work well and can help you.
/**
* Encrypt Token
*
* #param unknown $text
*/
private function rijndaelEncrypt($text) {
$iv_size = mcrypt_get_iv_size ( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB );
$iv = mcrypt_create_iv ( $iv_size, MCRYPT_RAND );
$key = 'your key';
return base64_encode ( mcrypt_encrypt ( MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv ) );
}
/**
* Decrypt
*
* #param unknown $text
*/
private function rijndaelDecrypt($text) {
$iv_size = mcrypt_get_iv_size ( MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB );
$iv = mcrypt_create_iv ( $iv_size, MCRYPT_RAND );
$key = 'your key';
// I used trim to remove trailing spaces
return trim ( mcrypt_decrypt ( MCRYPT_RIJNDAEL_256, $key, base64_decode ( $text ), MCRYPT_MODE_ECB, $iv ) );
}
See http://us3.php.net/manual/en/function.mcrypt-encrypt.php
first of all: MCRYPT_RIJNDAEL_256 is NOT(!) AES-256-CBC, if you want this encryption you have to use MCRYPT_RIJNDAEL_128 with an 265bit aka 32 character key.
This would be the php part:
function decrypt($data, $key) {
if(32 !== strlen($key)) $key= hash('SHA256', $key, true);
$data = base64_decode($data);
$data = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, str_repeat("\0", 16));
$padding = ord($data[strlen($data) - 1]);
return substr($data, 0, -$padding);
}
This php function includes padding which is an important part, because if the suplied data lenght is not a multiple of the key, you will get something odd.
For decoding we use some of my Node.js scripts with an emulated method of php's str_repeat for the iv:
var crypto = require('crypto');
function encrypt(data, key) {
key = key || new Buffer(Core.config.crypto.cryptokey, 'binary'),
cipher = crypto.createCipheriv('aes-256-cbc', key.toString('binary'), str_repeat('\0', 16));
cipher.update(data.toString(), 'utf8', 'base64');
return cipher.final('base64');
}
function str_repeat(input, multiplier) {
var y = '';
while (true) {
if (multiplier & 1) {
y += input;
}
multiplier >>= 1;
if (multiplier) {
input += input;
} else {
break;
}
}
return y;
}
NOTE: It is not recommend to use a static IV (Initialization vector)!
NOTE: JavaScript part is for Node.js using it's crypto library.
I hope this works for you.

Categories

Resources