Reputation: 53
I am using the below script: -
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
and running it on console as below: -
JSON.parse(CryptoJS.AES.decrypt("U2FsdGVkX1+S8UNrljj2STY8bBrYmr1qUbD2GYuJgIja1rzXY2y4BBkTf9GQxUGNyfRxP/BxiGIU7EFjnA2nTrM06ySr9bJySTjDDTqlDnY=", "87434313.47913419").toString(CryptoJS.enc.Utf8))
it successfully decrypts AES with key size 17 but when I run it using PHP with phpseclib, it throws the error Key of size 17 not supported
$data="U2FsdGVkX1+S8UNrljj2STY8bBrYmr1qUbD2GYuJgIja1rzXY2y4BBkTf9GQxUGNyfRxP/BxiGIU7EFjnA2nTrM06ySr9bJySTjDDTqlDnY=";
$key ="87434313.47913419";
$cipher = new AES('ecb');
$cipher->setKey($key);
echo $cipher->decrypt(base64_decode($data));
What am I doing wrong? Any help is appreciated !!!
Upvotes: 1
Views: 809
Reputation: 49351
As mentioned in the other answer, in the context of AES, CryptoJS can process both, a key (16, 24 or 32 bytes) or a password. This depends on the type of the second parameter in CryptoJS.AES.encrypt()/decrypt()
. A WordArray
is interpreted as a key, a string as a password.
In the current case a string is passed, which is thus processed as a password. CryptoJS generates an 8 bytes salt during encryption and derives a 32 bytes key and a 16 bytes IV from the salt and the password. The key derivation function is the OpenSSL function EVP_BytesToKey()
, which additionally requires a digest and an iteration count and for which CryptoJS uses the values MD5 and 1.
A possible implementation of EVP_BytesToKey()
in PHP for creating a key/IV pair for AES-256/CBC is:
// from: https://gist.github.com/ezimuel/67fa19030c75052b0dde278a383eda1b
function EVP_BytesToKey($salt, $password) {
$bytes = '';
$last = '';
// 32 bytes key + 16 bytes IV = 48 bytes.
while(strlen($bytes) < 48) {
$last = hash('md5', $last . $password . $salt, true);
$bytes.= $last;
}
return $bytes;
}
CryptoJS uses the OpenSSL format for the ciphertext, which consists of the ASCII encoding of Salted__
followed by the 8 bytes salt and the actual ciphertext. The posted ciphertext is the Base64 encoding of this value. During decryption, the salt and actual ciphertext must be separated:
// Separate salt and ciphertext
$dataB64 = 'U2FsdGVkX1+S8UNrljj2STY8bBrYmr1qUbD2GYuJgIja1rzXY2y4BBkTf9GQxUGNyfRxP/BxiGIU7EFjnA2nTrM06ySr9bJySTjDDTqlDnY=';
$data = base64_decode($dataB64);
$salt = substr($data, 8, 8);
$ciphertext = substr($data, 16);
Now the key and IV can be determined as follows:
// Derive key and iv
$passphrase = '87434313.47913419';
$keyiv = EVP_BytesToKey($salt, $passphrase);
$key = substr($keyiv, 0, 32); // hex encoded: e8db19b984ed9196fff1ce9150b73eafc4cb13abe69e6dcc1ea1528dd88982ff
$iv = substr($keyiv, 32, 16); // hex encoded: 47e26ab2bf3b66eda871d4929cc91029
and the actual ciphertext can be decrypted, e.g. with phpseclib:
use phpseclib\Crypt\AES;
$cipher = new AES('cbc');
$cipher->setKey($key);
$cipher->setIV($iv);
$plaintext = $cipher->decrypt($ciphertext);
echo json_decode($plaintext); // https://xcdn-209.bato.to/7002/32e/60af5c1a22f39459ededde23/
Since CryptoJS applies the OpenSSL format, the ciphertext is compatible with OpenSSL and can also be decrypted as follows:
openssl enc -d -aes-256-cbc -p -pass pass:87434313.47913419 -md md5 -A -a -in <file containing the U2FsdGVk...>
Note that EVP_BytesToKey()
is deemed insecure. The more secure way is to use a reliable key derivation function like Argon2 or PBKDF2 (the latter is also supported by CryptoJS) to derive key and IV and perform encryption with these values.
Upvotes: 2
Reputation: 97928
The error message seems fairly straight-forward to me: the key size you used isn't supported by that library. In fact, you appear to have only pasted part of the message, which goes on to list the key sizes which are supported: https://github.com/phpseclib/phpseclib/blob/7e38313802b62606cf27ddf573a7c47e88b5d33f/phpseclib/Crypt/AES.php#L118
'Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported'
So your problem is not understanding what the PHP can do, but understanding what the JS is doing with your input.
The clue to that is in the CryptoJS docs:
CryptoJS supports AES-128, AES-192, and AES-256. It will pick the variant by the size of the key you pass in. If you use a passphrase, then it will generate a 256-bit key.
So the library agrees that there are three valid key lengths, but supports passing in a "passphrase" instead which will be used to generate a key. The exact algorithm it uses to do that isn't documented; presumably it uses some Key Derivation Function with fixed parameters, so that the same passphrase will always produce the same key. If you really need to emulate it, you'll need to trace through the source code.
If you just need code that's compatible with both libraries, generate a random key of one of the supported lengths, and use that in both places.
Upvotes: 1