CryptoJS AES-128-ECB and PHP openssl_encrypt don't match - javascript

I have some code on PHP, which can't be edited and a database full of encrypted this way messages.
$key = '297796CCB81D2553B07B379D78D87618'
return $encrypted = openssl_encrypt($data, 'AES-128-ECB', $key);
I have to write some JS code to encrypt and decrypt these messages. I'm using CryptoJS for this purpose.
const key = '297796CCB81D2553B07B379D78D87618'
let k = CryptoJS.enc.Base64.parse(key)
let cypher = CryptoJS.AES.encrypt(this.text, k, {mode: CryptoJS.mode.ECB})
this.cypher = CryptoJS.enc.Base64.stringify(cypher.ciphertext)
I can not get these codes to produce the same results. For 'test' string i got following results: JS: H1AG6j/i/iSqifSNCG5JKw==, PHP: Nqrur4UMEicEMwJC39qq0A==
I'm trying to work this out for 3 days, but I cannot find the issue.
The only code I can edit is JS.

I figured out what problem is.
openssl_encrypt in PHP takes the key as a UTF-8 string. After taking enough length, it ignores next characters, so our key: '297796CCB81D2553B07B379D78D87618' trims to '297796CCB81D2553'.
This code is working:
// JS
const key = '297796CCB81D2553'
let k = CryptoJS.enc.Utf8.parse(key)
let cypher = CryptoJS.AES.encrypt(this.text, k, {mode: CryptoJS.mode.ECB})
this.cypher = CryptoJS.enc.Base64.stringify(cypher.ciphertext)

If you change the method to AES-256-ECB in your PHP part, than it will work with the same key (length). It is because of the CryptoJS.AES, that chooses the method by length of the key by itself.
https://cryptojs.gitbook.io/docs/#the-cipher-algorithms
AES-256-CBC EXAMPLE
/*
CryptoJS (JS) chooses the AES method by size of the key,
for AES-256 you need 256 bit key (32 1-byte chars).
For AES-128 you have to change substr() from "32" to "16" in this script below (in both PHP and JS part) and change $method in PHP part to "AES-128-CBC".
Default CryptoJS mode is CBC.
Openssl (PHP) AES-CBC cipher expects 128 bit iv (16 1-byte chars).
*/
/* JS */
// Substances
var data = "Data",
salt = CryptoJS.SHA256( "Salt" ),
iv = CryptoJS.SHA1( "Vector" ),
key = CryptoJS.SHA256( "Key" );
// Prepare substances
salt = salt.toString( CryptoJS.enc.Base64 );
iv = iv.toString( CryptoJS.enc.Base64 ).substr( 0, 16 );
iv = CryptoJS.enc.Utf8.parse( iv );
key = key.toString( CryptoJS.enc.Base64 ).substr( 0, 32 );
key = CryptoJS.enc.Utf8.parse( key );
// Cipher
var encrypted = CryptoJS.AES.encrypt( salt + data, key, { iv: iv });
// Results
console.log( "SALT\n" + salt );
console.log( "IV\n" + encrypted.iv.toString( CryptoJS.enc.Utf8 ));
console.log( "KEY\n" + encrypted.key.toString( CryptoJS.enc.Utf8 ));
console.log( "ENCRYPTED\n" + encrypted.toString());
/* PHP */
// Substances
$data = "Data";
$salt = openssl_digest( "Salt", "SHA256" );
$iv = openssl_digest( "Vector", "SHA1" );
$key = openssl_digest( "Key", "SHA256" );
// Method
$method = "AES-256-CBC";
// Prepare substances
$salt = base64_encode( hex2bin( $salt ));
$iv = substr( base64_encode( hex2bin( $iv )), 0, 16 );
$key = substr( base64_encode( hex2bin( $key )), 0, 32 );
// Cipher
$encrypted = openssl_encrypt( $salt .$data, $method, $key, 0, $iv );
// Results
var_dump([ "SALT" => $salt,
"IV" => $iv,
"KEY" => $key,
"ENCRYPTED" => $encrypted ]);
/*
Results CryptoJS (JS):
SALT: yDtwME1gxORm3S1FKrWukVtQJ2/EYSTt3j49voeKTf4=
IV: pX4G9FSRBn8DEoT7
KEY: S4VjDgGFIBNzA2jl8Ta6/iiIWJgtTgWF
ENCRYPTED
5y8MvyMiCUjTQJUxtRNZzuYSWwSoY9edMO0o6aQoSX0t4NZWnnViPotU8vkMtgfBuZ6F1FfE/ZJZvafdtGVHJQ==
Results openssl_encrypt (PHP):
SALT: yDtwME1gxORm3S1FKrWukVtQJ2/EYSTt3j49voeKTf4=
IV: pX4G9FSRBn8DEoT7
KEY: S4VjDgGFIBNzA2jl8Ta6/iiIWJgtTgWF
ENCRYPTED
5y8MvyMiCUjTQJUxtRNZzuYSWwSoY9edMO0o6aQoSX0t4NZWnnViPotU8vkMtgfBuZ6F1FfE/ZJZvafdtGVHJQ==
*/

Related

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)

crytpojs and openssl - diffrent aes outputs

I'm trying to get the same AES encryption output in PHP an Javascript
PHP code:
<?php
$rawKey = openssl_random_pseudo_bytes(32);
$iv = substr($rawKey, 16, 16);
$data = "5295760474330638";
$encryptedData = openssl_encrypt($data, "AES-128-CBC", $rawKey, OPENSSL_RAW_DATA, $iv);
echo base64_encode($rawKey); // CfOXpWTBF9mNcs7qSBw62OJpp+U/l/fu/YLh5aOXpXY=
echo base64_encode($iv); // 4mmn5T+X9+79guHlo5eldg==
echo base64_encode($encryptedData); // 3QQWLw7Q+Ff0Gz50XGsGORsOMWh9oeBiYprPr3/vl+I=
Javascript code:
encrypt () {
const key = this.CryptoJS.enc.Base64.parse('CfOXpWTBF9mNcs7qSBw62OJpp+U/l/fu/YLh5aOXpXY=');
const iv = this.CryptoJS.enc.Base64.parse('4mmn5T+X9+79guHlo5eldg==');
const data = '5295760474330638';
const encrypted: any = this.CryptoJS.AES.encrypt(data, key, {
mode: this.CryptoJS.mode.CBC,
iv: iv
});
console.log(encrypted.ciphertext.toString(this.CryptoJS.enc.Base64));
// QivkpDkujSt0JHAMwnENe6Qlkvgb8+3ddu7AcP401aM=
}
Why I'm getting diffrent outputs even though the key and iv are the same?

Decrypt in PHP with Salt, password, and type?

When I run Crypto-JS's encrypt function, I am given the base64-encoded following:
var crypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase", {mode: CryptoJS.mode.CBC}).toString();
==> "U2FsdGVkX19HKyOimD43Bl4ww/I40M+NQrscjti3ZnA="
How do I unencrypt this in PHP in the future? I have attempted using openSSL, mcrypt, etc, and nothin seems to work -- I guess I don't know how to deal with base64 encoding, salting, VI, and everything... Something goes wrong somewhere.
JS
// encrypt data with CryptoJS
var crypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
// get additional info from CryptoJS ecnrypted data
var data_base64 = crypted.ciphertext.toString(CryptoJS.enc.Base64);
var iv_base64 = crypted.iv.toString(CryptoJS.enc.Base64);
var key_base64 = crypted.key.toString(CryptoJS.enc.Base64);
PHP
$encrypted = base64_decode("data_base64"); // data_base64 from JS
$iv = base64_decode("iv_base64"); // iv_base64 from JS
$key = base64_decode("key_base64"); // key_base64 from JS
/* MCRYPT */
$plaintext = mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv );
// remove padding added by crypt algorithms
$plaintext = rtrim($plaintext, "\t\0 "); // remove tab-, zero- and space-padding
/***************************************/
/* OPENSSL */
$plaintext = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
// or
$plaintext = openssl_decrypt("data_base64", 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv); // works with base64 encoded data from JS

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