I want to encrypt and decrypt some string in Php and in Javascript and looking on the web, the best and safest way seems to be CryptoJs.
This post is not a duplicate of Encrypt with PHP, Decrypt with Javascript (cryptojs) because the output string it's not simple.
This is my code but the Js decrypting code doesn't work. What is it wrong?
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
</head>
<body>
<p>--- PHP ------------------</p>
<?php
function myCrypt($value, $passphrase, $iv){
$encrypted_data = openssl_encrypt($value, 'aes-256-cbc', $passphrase, true, $iv);
return base64_encode($encrypted_data);
}
function myDecrypt($value, $passphrase, $iv){
$value = base64_decode($value);
$data = openssl_decrypt($value, 'aes-256-cbc', $passphrase, true, $iv);
return $data;
}
$valTxt="MyText";
$pswd="MyPassword";
$vector="1234567890123412";
$encrypted = myCrypt($valTxt, $pswd, $vector);
$decrypted = myDecrypt($encrypted, $pswd, $vector);
echo "<p>Text to crypt --------> ".$valTxt." </p>";
echo "<p>Password: ".$pswd." </p>";
echo "<p>Vector: ".$vector." </p>";
echo "<p>TextEncrypt: ".$encrypted." </p>";
echo "<p>TextDecrypt: ".$decrypted." </p>";
?>
<br><br><br>
<p>--- Javascript ------------------</p>
<p>JS-DataEncrypt: --------- <span id="DataEncrypt"></span></p>
<p>JS-DataPassword: -------- <span id="DataPassword"></span></p>
<p>JS-DataVector: ---------- <span id="DataVector"></span></p>
<p>JS-TextDecrypted: ------- <span id="result"></span></p>
<script>
var DataEncrypt='<?php echo $encrypted;?>';
var DataPassword='<?php echo $pswd;?>';
var DataVector='<?php echo $vector;?>';
//var key = CryptoJS.enc.Hex.parse(DataPassword);
//var iv = CryptoJS.enc.Hex.parse(DataVector);
//var decrypted = CryptoJS.AES.decrypt(DataEncrypt, key, { iv: iv });
var decrypted = CryptoJS.AES.decrypt(DataEncrypt, DataPassword, { iv: DataVector });
decrypted= CryptoJS.enc.Utf8.stringify(decrypted)
document.getElementById("DataEncrypt").innerHTML = DataEncrypt;
document.getElementById("DataPassword").innerHTML = DataPassword;
document.getElementById("DataVector").innerHTML = DataVector;
document.getElementById("result").innerHTML = decrypted;
</script>
</body>
</html>
PS. Better if the output string ($encrypted) will be 16 digits A-Za-z0-9... is it possible changing 'aes-256-cbc'?
In the PHP code the following should be considered:
$passphrase
does not denote a passphrase, but the key. This key must be 32 bytes in size for the choice aes-256-cbc
. If it is too short, it is filled with 0 values, if it is too long, it is truncated. This is a common source of error, so a key of exactly 32 bytes should be used. If you want to work with a passphrase, you have to use a KDF (like PBKDF2).true
). If the data should be returned in binary form, the OPENSSL_RAW_DATA
flag must be set.The following sample PHP code (based on the posted code):
function myCrypt($value, $key, $iv){
$encrypted_data = openssl_encrypt($value, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encrypted_data);
}
function myDecrypt($value, $key, $iv){
$value = base64_decode($value);
$data = openssl_decrypt($value, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
return $data;
}
$valTxt="MyText";
$key="01234567890123456789012345678901"; // 32 bytes
$vector="1234567890123412"; // 16 bytes
$encrypted = myCrypt($valTxt, $key, $vector);
$decrypted = myDecrypt($encrypted, $key, $vector);
print($encrypted . "\n");
print($decrypted . "\n");
returns the following result:
1SF+kez1CE5Rci3H6ff8og==
MyText
The corresponding CryptoJS code for decryption is:
var DataEncrypt = "1SF+kez1CE5Rci3H6ff8og==";
var DataKey = CryptoJS.enc.Utf8.parse("01234567890123456789012345678901");
var DataVector = CryptoJS.enc.Utf8.parse("1234567890123412");
var decrypted = CryptoJS.AES.decrypt(DataEncrypt, DataKey, { iv: DataVector });
var decrypted = CryptoJS.enc.Utf8.stringify(decrypted);
console.log(decrypted);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
with the output MyText
which corresponds to the original plaintext.
It is important that the key is passed as WordArray
, so that it is interpreted as a key and not as a passphrase. For the conversion CryptoJS provides encoders (like CryptoJS.enc.Utf8
).
Regarding your question at the end: Ciphertexts are binary arbitrary sequences that can be converted to a string with special binary-to-text encodings (e.g. Base64 as in this case, or hexadecimal), which is generally longer than the raw data (Base64: 75% efficiency, hexadecimal: 50% efficiency, see here).
The representation of a ciphertext block with a number of alphanumeric characters (e.g. 16 chars) equal to the block size (e.g. 16 bytes for AES) is therefore generally not possible.
Note that converting to a string with a character set encoding such as UTF8 is not a solution either, but would corrupt the data.