I got into trouble when I tried to decrypt data encrypted with PHP's mcrypt using Node.js, so here is a note on the cause and solution.
situation
I received data that I was told was encrypted with AES256-CBC in PHP, but when I tried to decrypt it in Node.js, I got an error.
The data was encrypted and there was nothing more we could do about it, so we asked to see the source of the encrypted part, which was as follows.
<?php function encode($data){ $key = "1234567890123456"; $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); $encoded_data = mcrypt_encrypt( MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv); return base64_encode($iv . $encoded_data); } ?>
It was encrypted with mcrypt, a PHP encryption library, and looked fine, almost identical to the sample code in the mcrypt manual.
However, when I actually built the PHP environment, created the encrypted data, and tried it, the same error occurred and it was no good.
Reason
What I noticed when I touched PHP during the process was that the length of the key was 128 bits and the length of the IV obtained from mcrypt_create_iv()
was 32 bytes, which was different from the length we expected (256-bit key and 16-byte IV size), even though the key is AES256-CBC.
In my research, I found various articles about encryption with mcrypt but not decryption with anything other than mcrypt.
- https://stackoverflow.com/questions/49997338/mcrypt-rijndael-256-to-openssl-aes-256-ecb-conversion/50000095
- https://stackoverflow.com/questions/56926812/decrypt-aes-cbc-256-mcrypt-rijndael-encrypted-in-php-decrypt-on-golang
- https://stackoverflow.com/questions/6038620/aes-encrypt-in-node-js-decrypt-in-php-fail
And the shocking conclusion,
The encryption we call AES256-CBC (MCRYPT_RIJNDAEL_256 and MCRYPT_MODE_CBC) in PHP's mcrypt is different from the common AES256-CBC encryption, which cannot be decrypted with AES256-CBC."
It was .......
Then what to do? Fortunately, Node.js has a library that uses PHP's mcrypt library without modification, and I decided to use it.
Solution
Since only the algorithm, mode, and padding were ported for "cryptian," we proceeded to check the rest in PHP, and in the end, the data encrypted with the aforementioned code could be decrypted with the following Node.js.
import * as stream from 'stream' const cryptian = require('cryptian'); const mstream = require('memory-streams'); function decodeMcrypt(data: string) { return new Promise((resolve, reject) => { const buff = Buffer.from(data, 'base64'); // iv size is 32 bytes const iv = buff.slice(0, 32); const encdata = buff.slice(32); const key_str = '1234567890123456'; const key = Buffer.from(key_str); const algorithm = cryptian.algorithm.Rijndael256(); algorithm.setKey(key); const decipher = new cryptian.mode.cbc.Decipher(algorithm, iv); const rstrm = new stream.Readable(); const wstrm = new mstream.WritableStream(); // Padding is Pkcs7 const dstrm = cryptian.createDecryptStream( decipher, cryptian.padding.Pkcs7 ); wstrm.on('finish', () => { resolve(wstrm.toBuffer().toString('utf-8')); }); rstrm.pipe(dstrm).pipe(wstrm); rstrm.push(encdata); rstrm.push(null); }); } (async () => { const encoded_data = "xxxxxxxxxx"; const decoded_data = await decodeMcrypt(encoded_data); console.log(decoded_data); })();
There are three parameters that determine the encryption method: "algorithm," "mode," and "padding." From the encryption code, we know that "algorithm" is "MCRYPT_RIJNDAEL_256" and "mode" is "MCRYPT_MODE_CBC," but the "padding" is unknown and was determined by a general PKCS7" was found by a total guess.
The library only supports stream processing, so it is a bit of a hassle to set up.
application
Since "cryptian" uses PHP's mcrypt as-is, it should be possible to decrypt data encrypted with other methods of mcrypt.
impressions
It is always troublesome and we tend to verify decryption with the same library as when we encrypted and assume it is OK, but it is quite scary.
I became concerned, so I tried AES256-CBC encryption with PHP openssl and decryption with Node.js AES256-CBC, but that one was successfully decrypted, so I was relieved that Node.js AES256-CBC decryption was ok.
About mcrypt
Since mcrypt is already a deprecated library, if left alone, mcrypt itself may disappear and no one will be able to decrypt it...
So it is better to migrate from mcrypt as soon as possible.
Compatibility
I was looking at the Wikipedia page on "AES" and found the reason why it is not compatible with AES.
The "RIJNDAEL" encryption used by mcrypt is the original encryption of "AES" and has multiple block sizes, key sizes, and padding formats, and the implementer chooses which one to use. And "AES" has a standard that determines which one to use.
And unfortunately, what was chosen for mcrypt's "RIJNDAEL" and what was chosen as the standard for "AES" were not compatible because they were not the same.
Even though the mechanism is the same, the setting values are different, so this can't be decrypted with "AES" no matter how hard I try.