crytpojs and openssl - diffrent aes outputs - javascript

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?

Related

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)

How to decrypt AES 128-CBC by Crypto-JS?

I have a PHP code that decrypts payload with a secret key, I'm trying to write exactly the same code in JavaScript using the crypto-js library, but I get the wrong result.
First 16 bytes in payload - is vector, remaining part - is useful information.
Working code in PHP - https://ideone.com/NJXkRK
function getPayload($app_secret_key, $data) {
// Get the encryption key (16 first bytes of the app's client_secret key)
$encryption_key = substr($app_secret_key, 0, 16);
// Decrypt payload
$json_data = aes_128_decrypt($encryption_key, $data);
// Decode json
$json_decoded = json_decode($json_data, true);
return $json_data;
}
function aes_128_decrypt($key, $data) {
// Ecwid sends data in url-safe base64. Convert the raw data to the original base64 first
$base64_original = str_replace(array('-', '_'), array('+', '/'), $data);
// Get binary data
$decoded = base64_decode($base64_original);
// Initialization vector is the first 16 bytes of the received data
$iv = substr($decoded, 0, 16);
// The payload itself is is the rest of the received data
$payload = substr($decoded, 16);
// Decrypt raw binary payload
$json = openssl_decrypt($payload, "aes-128-cbc", $key, OPENSSL_RAW_DATA, $iv);
//$json = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $payload, MCRYPT_MODE_CBC, $iv); // You can use this instead of openssl_decrupt, if mcrypt is enabled in your system
return $json;
}
// Get payload from the GET and process it
$ecwid_payload = "ng7W9c9jLhkX7ATMpafNAd5Vt_skEaFAqnQaw0Ing1iwYQOwB0Q_CuCS8yQeHeorTdCpZWDTNrzhcq_umX7IaAFUPPgs0zyddY7Er1tA0aze5kWGHUV54fJHoVEJHMmVEi-G5g8ZnNopIFu0YQgQqLpCq8TP2zFJunSTA7VXHTmqHNAD2JXaUb-VylcJWzgV0vaCoGyHqaPbsNNw6HSWkAzhh8dLmsYB0uzsZ_zl3wVXubCL4p2N53PmNPBLCgoC";
$client_secret = "zcKf1Zt0UsO43S46Un3pxIgs91R1xMGs";
$result = getPayload($client_secret, $ecwid_payload);
print($result);
Not working JS code
function getPayload() {
const payload =
"ng7W9c9jLhkX7ATMpafNAd5Vt_skEaFAqnQaw0Ing1iwYQOwB0Q_CuCS8yQeHeorTdCpZWDTNrzhcq_umX7IaAFUPPgs0zyddY7Er1tA0aze5kWGHUV54fJHoVEJHMmVEi-G5g8ZnNopIFu0YQgQqLpCq8TP2zFJunSTA7VXHTmqHNAD2JXaUb-VylcJWzgV0vaCoGyHqaPbsNNw6HSWkAzhh8dLmsYB0uzsZ_zl3wVXubCL4p2N53PmNPBLCgoC";
const key = "zcKf1Zt0UsO43S46Un3pxIgs91R1xMGs";
// Get the encryption key (16 first bytes of the app's client_secret key)
const encryption_key = key.substr(0, 16);
// Decrypt payload
const base64_original = payload.replace(/-/gi, "+").replace(/_/gi, "/");
const data = aes_128_decrypt(encryption_key, base64_original);
console.log(data);
}
function aes_128_decrypt(password, data) {
const decoded = atob(data);
let iv = decoded.substr(0, 16);
let payload = decoded.substr(16);
iv = CryptoJS.enc.Hex.parse(iv);
const decrypted = CryptoJS.AES.decrypt(payload, password, {
iv: iv,
padding: CryptoJS.pad.NoPadding
});
return decrypted.toString();
}
window.onload = function() {
getPayload();
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Please help me to improve this JS code
There are several issues in the JavaScript code:
The key must not be passed as a string, but as a WordArray (otherwise CryptoJS uses a key derivation function).
IV and ciphertext are not determined correctly.
The ciphertext must be passed as CipherParams object (or as Base64 encoded string).
The padding must be PKCS7 (decryption would also work with NoPadding, but the padding bytes wouldn't be removed).
The plaintext must be Utf8 decoded (.toString() hex encodes by default)
For details see the CryptoJS documentation, especially the chapters The Cipher Input and The Cipher Output.
The following JavaScript code decrypts the ciphertext:
function getPayload() {
const payload = "ng7W9c9jLhkX7ATMpafNAd5Vt_skEaFAqnQaw0Ing1iwYQOwB0Q_CuCS8yQeHeorTdCpZWDTNrzhcq_umX7IaAFUPPgs0zyddY7Er1tA0aze5kWGHUV54fJHoVEJHMmVEi-G5g8ZnNopIFu0YQgQqLpCq8TP2zFJunSTA7VXHTmqHNAD2JXaUb-VylcJWzgV0vaCoGyHqaPbsNNw6HSWkAzhh8dLmsYB0uzsZ_zl3wVXubCL4p2N53PmNPBLCgoC";
const key = "zcKf1Zt0UsO43S46Un3pxIgs91R1xMGs";
// Get the encryption key (16 first bytes of the app's client_secret key)
//const encryption_key = key.substr(0, 16);
const encryption_key = CryptoJS.enc.Utf8.parse(key.substr(0, 16)); // Parse the key into a WordArray
// Decrypt payload
const base64_original = payload.replace(/-/gi, "+").replace(/_/gi, "/");
const data = aes_128_decrypt(encryption_key, base64_original);
console.log(data.replace(/(.{56})/g,'$1\n')); // {"store_id":20553036,"access_token":"secret_a9TmTJfRt3gyvxjJ9UwYjs9VQip3F7rp","public_token":"public_QQ99gUwVGdvKuZbLLyNZzDsvXF5iF3gh","view_mode":"PAGE","lang":"ru"}
//console.log(JSON.parse(data)); // Convert JSON string into JavaScript object (optional)
}
function aes_128_decrypt(password, data) {
/*
const decoded = atob(data);
let iv = decoded.substr(0, 16);
let payload = decoded.substr(16);
iv = CryptoJS.enc.Hex.parse(iv);
*/
var ivCiphertext = CryptoJS.enc.Base64.parse(data); // Parse data into a WordArray
var iv = CryptoJS.lib.WordArray.create(ivCiphertext.words.slice(0, 16 / 4)); // Separate iv
var payload = CryptoJS.lib.WordArray.create(ivCiphertext.words.slice(16 / 4)); // and ciphertext
//const decrypted = CryptoJS.AES.decrypt(payload, password, {
const decrypted = CryptoJS.AES.decrypt(
{
ciphertext: payload // Pass CipherParams object
},
password,
{
iv: iv
//padding: CryptoJS.pad.NoPadding // Apply PKCS7 padding
});
//return decrypted.toString();
return decrypted.toString(CryptoJS.enc.Utf8); // Utf8 decode plaintext
}
window.onload = function() {
getPayload();
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

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

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==
*/

AES 256 on the client side (JS) and in the server (PHP)

I'm trying to encrypt and decrypt data on the server side and the client using the same type of operation, which is AES-256.
On the server I use PHP and client I use CryptoJS so far I could only encrypt and decrypt the client on the server, see the code:
JS
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/pbkdf2.js"></script>
<script>
var salt = CryptoJS.lib.WordArray.random(128/8);
var key256Bits500Iterations = CryptoJS.PBKDF2("Secret Passphrase", salt, { keySize: 256/32, iterations: 500 });
var iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
var encrypted = CryptoJS.AES.encrypt("Message", key256Bits500Iterations, { iv: iv });
var data_base64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
var iv_base64 = encrypted.iv.toString(CryptoJS.enc.Base64);
var key_base64 = encrypted.key.toString(CryptoJS.enc.Base64);
</script>
PHP
<?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
$plaintext = rtrim( mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv ), "\t\0 " );
How can I encrypt and decrypt data on both sides (client and server) so that communicate in the same language using PHP and CryptoJS?
Your code looks fine apart from a padding mismatch. CryptoJS uses PKCS#5/PKCS#7 padding by default whereas MCrypt only supports ZeroPadding.
If you're only sending textual plaintexts, then you can safely use
CryptoJS.AES.encrypt("Message", key, { iv: iv, padding: CryptoJS.pad.ZeroPadding });
If not, then you should use proper pkcs7unpad in PHP:
$plaintext = pkcs7unpad( mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv ), 16 );
Other problems with your code are that you directly use CryptoJS.AES.encrypt(...).toString(). This will create an OpenSSL formatted string which is not purely the ciphertext. You need to use
CryptoJS.AES.encrypt(...).ciphertext.toString(CryptoJS.enc.Base64);
to also be sure about the encoding.
Right now, this is only obfuscation, since you're sending the key along with the ciphertext. I suspect that you want to derive the key in PHP too. If yes, then you will only need to send the random salt along with the ciphertext under the assumption that the server knows the password.
PHP provides a PBKDF2 implementation from version 5.5 onwards.
Full JavaScript part without PBKDF2 involvement:
var message = 'My string - Could also be an JS array/object';
var iv = 'a1a2a3a4a5a6a7a8b1b2b3b4b5b6b7b8';
var key = 'c1c2c3c4c5c6c7c8d1d2d3d4d5d6d7d8c1c2c3c4c5c6c7c8d1d2d3d4d5d6d7d8'; // 256-bit hex encoded
var keyBytes = CryptoJS.enc.Hex.parse(key);
var ivBytes = CryptoJS.enc.Hex.parse(iv);
var encrypt = CryptoJS.AES.encrypt(message, keyBytes, {
iv: ivBytes,
padding: CryptoJS.pad.ZeroPadding
}).ciphertext.toString(CryptoJS.enc.Base64);
produces:
j86KHBVRsDGKUnOiYdkEotsFL/lY/1tzz/h3Ay+vlEX11fC055m7vaF6q7w13eUj
Full PHP part without PBKDF2 involvement:
<?php
$iv = 'a1a2a3a4a5a6a7a8b1b2b3b4b5b6b7b8';
$key = 'c1c2c3c4c5c6c7c8d1d2d3d4d5d6d7d8c1c2c3c4c5c6c7c8d1d2d3d4d5d6d7d8';
$ct = 'j86KHBVRsDGKUnOiYdkEotsFL/lY/1tzz/h3Ay+vlEX11fC055m7vaF6q7w13eUj';
$ivBytes = hex2bin($iv);
$keyBytes = hex2bin($key);
$ctBytes = base64_decode($ct);
$decrypt = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $keyBytes, $ctBytes, MCRYPT_MODE_CBC, $ivBytes));
echo $decrypt;
produces:
My string - Could also be an JS array/object
The same is possible with the OpenSSL extension:
<?php
$iv = 'a1a2a3a4a5a6a7a8b1b2b3b4b5b6b7b8';
$key = 'c1c2c3c4c5c6c7c8d1d2d3d4d5d6d7d8c1c2c3c4c5c6c7c8d1d2d3d4d5d6d7d8';
$ct = 'j86KHBVRsDGKUnOiYdkEotsFL/lY/1tzz/h3Ay+vlEX11fC055m7vaF6q7w13eUj';
$ivBytes = hex2bin($iv);
$keyBytes = hex2bin($key);
$ctBytes = base64_decode($ct);
$decrypt = openssl_decrypt($ctBytes, "aes-256-cbc", $keyBytes, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $ivBytes);
echo($decrypt);

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

Categories

Resources