I tried to decrypt my api response object which was encrypted in php. Encryption is working and decrypting in PHP worked perfectly too. But decrypting it in ionic 5 always return "Malformed UTF-8 data error".
When decrypted back in PHP:
Array
(
[name] => fagbemi Ayodele
[username] => fagbemiayodele48
[email] => [email protected]
[createTime] => 2022-08-05 03:28:41
[updateTime] =>
[gender] => male
[telephone] => 07069605705
[constellation] => 0
[userId] => 130
[loginTime] => 1672319617
[avatar] => http://localhost:83/forum_new/assets/logo/avatar.png
)
PHP encryption code:
function encrypt($value, string $passphrase)
{
$salt = openssl_random_pseudo_bytes(8);
$salted = '';
$dx = '';
$data = [];
while (strlen($salted) < 48) {
$dx = md5($dx . $passphrase . $salt, true);
$salted .= $dx;
}
$key = substr($salted, 0, 32);
$iv = substr($salted, 32, 16);
$encrypted_data = openssl_encrypt(json_encode($value), 'aes-256-cbc', $key, true, $iv);
$data["ct"] = base64_encode($encrypted_data);
$data["iv"] = bin2hex($iv);
$data["s"] = bin2hex($salt);
return json_encode($data);
}
Ionic 5 (Typescript) decryption code
async base64Decrypt(jsonStr: string, passphrase: string): Promise<any> {
const json = JSON.parse(jsonStr);
const salt = CryptoJS.enc.Hex.parse(json["s"]);
const iv = CryptoJS.enc.Hex.parse(json["iv"]);
const ct = CryptoJS.enc.Base64.parse(json["ct"]);
const concatedPassphrase = passphrase salt;
const md5 = [];
md5[0] = CryptoJS.MD5(concatedPassphrase);
let result = md5[0];
for (let i = 1; i < 3; i ) {
md5[i] = CryptoJS.MD5(CryptoJS.lib.WordArray.create(md5[i - 1]).concat(concatedPassphrase));
result = CryptoJS.lib.WordArray.create(result).concat(md5[i]);
}
const key = result.toString(CryptoJS.enc.Hex).substring(0, 32);
const data = CryptoJS.AES.decrypt({ciphertext: ct}, CryptoJS.enc.Hex.parse(key), {iv: iv});
return JSON.parse(data.toString(CryptoJS.enc.Utf8));
}
decryption key
"key":"8cd826ce5315e72a"
Encrypted data:
{"ct":"2A5q0EUDjgp4JLO341jEtJz7hoJzCm6vr9AKmz3TygAmiEXz8 phACCjUwBAadf4KRUCI\/CFLrd7zjr3gp8ttsBZv3yQwYjCKVyYnMc4hhugJkstNi80ySnMCGMzKoampUBs2gtxgbEtI0a6UPJMAC3Lq8LcdQxKNdwItfbv4JbBa1 N\/akIos8\/4IgHT 47AImd6VuXouSf\/K3NUxzUDzEmBmENozi21yELHZbjkC4EcsNKoYp8mDRQA6hqRVaVqM TtkSZNdw5MtOz5mvp\/FJ76SRXC8gXP1jL6y93s z7e1Vlje79pTRhSCcLFDdczib H2t2fnDZOXwaQmjZQXUV2FoloLkfWzvCF\/Rh\/AX3wtGLpp43qAH8ec6vrFOgFD1TqQqWFloOmelmp6aDMhULTG33fHmHPMNbkHWRA k=","iv":"d6ac6a0fb929e15909942e2aceae8762","s":"22a3d46564b07b91"}
How can I resolve the error?
ERROR Error: Uncaught (in promise): Error: Malformed UTF-8 data Error: Malformed UTF-8 data at Object.stringify
CodePudding user response:
The PHP code implements the logic of EVP_BytesToKey()
for key derivation, which is also the internal key derivation function of CryptoJS.
Therefore, decryption can be significantly simplified on the JavaScript side. To do this, the passphrase must be passed as string and salt and ciphertext encapsulated in a CipherParams
object:
var passphrase = "8cd826ce5315e72a";
var jsonStr = `{"ct":"2A5q0EUDjgp4JLO341jEtJz7hoJzCm6vr9AKmz3TygAmiEXz8 phACCjUwBAadf4KRUCI\/CFLrd7zjr3gp8ttsBZv3yQwYjCKVyYnMc4hhugJkstNi80ySnMCGMzKoampUBs2gtxgbEtI0a6UPJMAC3Lq8LcdQxKNdwItfbv4JbBa1 N\/akIos8\/4IgHT 47AImd6VuXouSf\/K3NUxzUDzEmBmENozi21yELHZbjkC4EcsNKoYp8mDRQA6hqRVaVqM TtkSZNdw5MtOz5mvp\/FJ76SRXC8gXP1jL6y93s z7e1Vlje79pTRhSCcLFDdczib H2t2fnDZOXwaQmjZQXUV2FoloLkfWzvCF\/Rh\/AX3wtGLpp43qAH8ec6vrFOgFD1TqQqWFloOmelmp6aDMhULTG33fHmHPMNbkHWRA k=","iv":"d6ac6a0fb929e15909942e2aceae8762","s":"22a3d46564b07b91"}`;
var json = JSON.parse(jsonStr);
var cipherParams = {ciphertext: CryptoJS.enc.Base64.parse(json.ct), salt: CryptoJS.enc.Hex.parse(json.s)}; // apply a CipherParams object
var decryptedData = CryptoJS.AES.decrypt(cipherParams, passphrase).toString(CryptoJS.enc.Utf8); // decrypt and UTF-8 decode
var decryptedDataJSON = JSON.parse(decryptedData); // convert to JavaScript object
console.log(decryptedDataJSON);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
A few notes:
CryptoJS derives the IV internally, along with the key, so it does not need to be explicitly passed to the decrypting side.
Instead of a
CipherParams
object, salt and ciphertext can alternatively be passed in the Base64 encoded OpenSSL format toCryptoJS.AES.decrypt()
. The OpenSSL format corresponds to the ASCII encoding ofSalted__
, followed by the 8 bytes salt and the ciphertext. In this case, it would be efficient to format the data accordingly already on the PHP side:... return base64_encode("Salted__" . $salt . $encrypted_data);
In
openssl_encrypt()
,OPENSSL_RAW_DATA
should be passed as 4th parameter instead oftrue
(although both are numerically identical, the latter is more transparent).EVP_BytesToKey()
is considered insecure nowadays. Instead (at least) the more reliable PBKDF2 should be applied, which is supported by both PHP and CryptoJS.