I'm receiving an encrypted message from an external party. They're using php and mcrypt for encryption with the following configuration
algo: rijndael-256
mode: ecb
example encrypted message: "a364cb3bb55c2788b25f04ef6867d770771a7d0fdd44462b40233ea76a8bd00d". Its original message is "thank you"
The following images showing the configuration
From my side, I'm using nodejs to decrypt. I have a working version with node-mcrypt binding. But I want to avoid native modules so I'm looking for a pure javascript solution.
I tried node's crypto but its aes-256-ecb produces different (unreadable) output
The working code with MCrypt binding in nodejs:
const decryptAES256 = (encrypted: string, password: string): string => {
try {
const desEcb = new MCrypt('rijndael-256', 'ecb');
desEcb.open(md5(password));
const ciphertext = desEcb.decrypt(new Buffer(encrypted, 'hex'));
const plain = ciphertext.toString();
const isUTF8 = plain.split('').every(c => c.charCodeAt(0) <= 256);
if (!isUTF8) {
throw new Error('Invalid Input');
}
const unpadding = plain
.split('')
.filter(c => c.charCodeAt(0))
.join('');
return unpadding;
} catch (ex) {
console.error('Invalid token');
}
};
When you state "rijndael-256" that maybe specifying a 256-bit block size which is not AES. AES has one block size: 128-bits.
This seems to be born out by the encrypted output which is 32-binary bytes and for an input of "thank you" the output should be one block so it seems the block size is 32-bytes (256-bits).
Do not use mcrypt, it will just cause interoperability problems.
Make sure you are using AES which can be rijndael with a 128-bit block size.
Do not confuse the key size with the block size.
If the input data is not always a multiple of the block size then padding must be added to the input data], specify padding, PKCS#7 (née PKCS#5) padding which may be the default. mcrypt does not support standard padding. If you must interoperate with mcrypt you will need to specify no padding and perform the padding yourself.
After realizing that rijndael 256 is not equivalent to aes 256 (with 128 version is), I opt to find a rijndael specific lib in pure javascript and this is a working code:
const Rijndael = require('rijndael-js');
describe('cipher', () => {
it('should work', () => {
const text = 'thank you';
// + String.fromCharCode(0).repeat(5)
const key = '94a08da1fecbb6e8b46990538c7b50b2';
const cipher = new Rijndael(key, 'ecb');
const ciphertext = cipher.encrypt(text, 256);
expect(ciphertext.toString('hex')).toBe(
'a364cb3bb55c2788b25f04ef6867d770771a7d0fdd44462b40233ea76a8bd00d'
);
const plaintext = cipher.decrypt(ciphertext, 256);
const unpadded = plaintext.toString().replace(/\u0000*$/g, '');
expect(unpadded).toBe(text);
});
});
Related
Goodmorning,
I am trying to implement some new things on a legacy system.
To manage the authentication I need to pass an encrypted string that comes from a nodejs source to a php page and decrypt it and verify it.
The following code is what I have tried to do, the main problem is that I cannot change the nodejs code, even if it is deprecated, so I have to try to implement the correct way to decrypt it in php, the password is the same, the algorithm is aes-256-ctr and there is no padding and initialization vector.
NODE.JS CODE
function encrypt(text) {
var cipher = crypto.createCipher(algorithm, password())
cipher.setAutoPadding(false);
var crypted = cipher.update(text, 'utf8', 'hex')
crypted += cipher.final('hex')
return crypted
}
function decrypt(text) {
var decipher = crypto.createDecipher(algorithm, password())
decipher.setAutoPadding(false);
var dec = decipher.update(text, 'hex', 'utf8')
dec += decipher.final('utf8')
return dec
}
PHP CODE
function decrypt($text)
{
return openssl_decrypt(hex2bin($text), _algorithm_, password(), OPENSSL_NO_PADDING);
}
Somehow these two decrypt works in a different way, what can I do to make the php one behave as the nodejs decrypt?
EDIT: as suggested by #Chris Haas I checked if the conversion by and from hex or utf-8 are the same, and I can confirm that they are the same
var Buffer = require('buffer').Buffer
const buf = Buffer.from('hello world', 'utf8');
console.log(buf.toString('hex'));
echo bin2hex("hello world");
I have this code in PHP (can't modify)
<?php
$myPlain = "123456789012345678900000";
$myKey = md5($myPlain, true);
$myKey .= substr($myKey, 0,8);
$encrypted = openssl_encrypt($myPlain, 'des-ede3', $myKey, OPENSSL_RAW_DATA);
print(base64_encode($encrypted));
This code returns
FTYDrZTZMjVBv5Fk/xcfFxJASaizzrmoPts7fGDvWjc=
When I try to replicate this in NodeJS
function testEde3(myKey, myPlain) {
try {
let md5Key = crypto.createHash('md5').update(myKey, 'utf-8').digest("base64").substr(0, 24);
console.log(md5Key); //outputs 4o1aJrSWN3bSfjuIX6VXgA==
console.log(md5Key.length); //outputs 24
const cipher = crypto.createCipheriv('des-ede3', md5Key, null);
let encrypted = cipher.update(myPlain, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted;
} catch ( ex ) {
return ex;
}
}
const myKey = "123456789012345678900000";
const myPlain = "123456789012345678900000";
const hash = testEd3(myKey, myPlain);
console.log(`Hash is: ${hash}`);
The output is
Hash is: lDQX9OGsopKOt6P9WQwekGsKDQGFpfGW50zbs3HrOfQ=
I'm thinking the problem is on MD5. If I try to encrypt without md5, the results are the same.
Thanks
The key in Php code consists of the 16 bytes of the MD5 hash, to which the first 8 bytes of the MD5 hash are appended, resulting in a 24 bytes key (as required for 3DES). This is currently not implemented in the NodeJS code, but can be achieved e.g. by:
let md5Key = crypto.createHash('md5').update(myKey, 'utf-8').digest();
md5Key = Buffer.concat([md5Key, md5Key.slice(0, 8)]);
With this change, the NodeJS code generates the ciphertext of the PHP code with the same input data.
des-ede3 means 3DES in ECB mode. Note that 3DES is outdated and slow and should be replaced by AES. ECB mode is generally insecure. Encrypting the key also makes little sense (but may only be for testing purposes).
My problem is as follows:
I have a PHP script that is responsible for encrypting a string using AES-256-CBC encryption. This script uses the openssl lib and returns an X result.
<?php
class AES
{
const PRIVATE_KEY = 'abcdefghijklmnnoabcdefghijklmnno';
const ENCRYPT_METHOD = 'aes-256-cbc';
const VECTOR = 'abcdefghijklmnno';
public function encryptData($data)
{
while(strlen($data) < 16) $data .= "\0";
return openssl_encrypt($data, self::ENCRYPT_METHOD, self::PRIVATE_KEY, OPENSSL_ZERO_PADDING, self::VECTOR);
}
public function encryptDataL($data)
{
return openssl_encrypt($data, self::ENCRYPT_METHOD, self::PRIVATE_KEY, 0, self::VECTOR);
}
public function decryptData($data)
{
return openssl_decrypt($data, self::ENCRYPT_METHOD, self::PRIVATE_KEY, OPENSSL_ZERO_PADDING, self::VECTOR);
}
}
$aes = new AES();
echo $aes->encryptData("abcdefghijkl");
echo "\n";
echo $aes->encryptDataL("{\"REQUEST\": [{\"MSISDN\": \"32156489721\",\"IDPRODUCT\": 123,\"IDOPERATOR\": 12345,\"OUTPUTFORMAT\": \"JSON\"}],\"OUTPUTFORMAT\": \"json\"}");
?>
when I run a JS script, responsible for doing the same, but using the Crypto lib, the result obtained is different from the previous X.
const crypto = require('crypto');
const cipher = crypto.createCipheriv('aes-256-cbc', 'abcdefghijklmnnoabcdefghijklmnno', 'abcdefghijklmnno');
let crypted = cipher.update(data, 'utf8', 'base64');
crypted += cipher.final('base64');
The results of the scripts differ, even though, in theory, the encryption should be the same.
An example of return is as follows:
For the php script: input -> ^y3Hk3JKGGgA output -> eTqD5Op389QS/TOoui5kAQ==
For the js script: input -> ^y3Hk3JKGGgA output -> HHfskOE1N+QxdGt9MTai5A==
The desired result is the PHP script, but I need to run the code in JS, can someone explain to me what I may be doing wrong?
I tried different ways to execute the createCipheriv method, but they all return the same result (different from what I need, which is the result obtained through the PHP script)
Thank you in advance.
Thank you guys for trying to help, indeed I posted the question lacking some informations (actually when the question was made I didn't have all the pieces of information I needed).
But posting here some facts and the solution encountered for my case.
The different results in the cases above only happen for the first PHP function ("encryptData"), responsible for encrypting small texts. The second, responsible for encrypting large texts (more than 16 bits) worked fine, both in PHP and JS scripts.
The solution I encountered was making the padding needed for the AES-256 algorithm by myself. The padding function provided by the Crypto lib didn't work, at least for my case.
So I disabled the padding in my cypher class and make sure that the data sent to be encrypted was padded correctly until the length is multiple of 16. The end's code is below.
encryptWithAES256(data) {
// added padding until data length is multiple of 16
let paddedData = data;
while (paddedData.length % 16 !== 0) {
paddedData += '\0';
}
// ciphers data
const cipher = crypto.createCipheriv('aes-256-cbc', encodeKey, IV);
cipher.setAutoPadding(false);
let crypted = cipher.update(paddedData, 'utf8', 'base64');
crypted += cipher.final('base64');
return crypted;
}
I have a next problem
On Node.js I have a next code
var iv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); //it's for tests, later it will be dynamically generated
var key256Bits = 'A5178B6A965AACF3CD60B07A15061719';
var cipher = CryptoJS.AES.encrypt(
'Some text',
key256Bits,
{
iv: iv,
padding:CryptoJS.pad.ZeroPadding
}
).toString();
Then when I try to decode it with phpseclib
$key = 'A5178B6A965AACF3CD60B07A15061719';
$data = /*text encrypted by JS*/;
$cipher = new AES();
$cipher->setKeyLength(256);
$cipher->setKey($key);
$res = $cipher->decrypt($data);
And then $res becomes an empty string
What do I do wrong?
If you pass in a string to CryptoJS.<cipher>.encrypt as a key, CryptoJS treats it as a password and will derive the actual key from that using OpenSSL's EVP_BytesToKey with a random salt and one iteration of MD5.
phpseclib doesn't have an implementation of that, so you could just pass in the actual key:
var key256Bits = CryptoJS.enc.Utf8.parse('A5178B6A965AACF3CD60B07A15061719');
Since this key is only 32 hexits long, it only has 128 bit of entropy, but still uses AES-256. You need 64 hexits which you can decode before use to get 32 bytes for a secure key size.
Also, phpseclib implements PKCS#7 padding, so you need to use
padding: CryptoJS.pad.Pkcs7
in CryptoJS.
Example JavaScript code:
var iv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); //it's for tests, later it will be dynamically generated
var key256Bits = CryptoJS.enc.Utf8.parse('A5178B6A965AACF3CD60B07A15061719');
var cipher = CryptoJS.AES.encrypt(
'Some text',
key256Bits,
{
iv: iv,
padding: CryptoJS.pad.Pkcs7
}
).toString();
console.log(cipher)
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
In PHP, you need to make sure to decode the ciphertext before use:
$data = base64_decode("IWkBG3A46rNrxwWN2JD7xQ==");
$key = 'A5178B6A965AACF3CD60B07A15061719';
$cipher = new AES();
$cipher->setKeyLength(256);
$cipher->setKey($key);
$res = $cipher->decrypt($data);
var_dump($res);
Security consideration:
If you're using only symmetric encryption you need the exact same key at the server and the client. If you send the encryption key from the server to the client or the other way around you need to encrypt your symmetric encryption key. The easiest way to do this would be to use TLS. If you use TLS, then the data as well as key are encrypted, so you don't need to encrypt it yourself. This doesn't provide any security, just a little bit of obfuscation. You should read: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/
I'm having an issue encrypting my string in C++ and then decrypting in PHP. On the C++ side, I think everything is going fine. Below is my code for the C++ side.
unsigned char inbuffer[1024];
unsigned char outbuffer[1024];
unsigned char oneKey[] = "abc";
AES_KEY key;
AES_set_encrypt_key(oneKey, 128, &key);
string straa("hello world\n");
memcpy((char*)inbuffer, straa.c_str(), 13);
AES_encrypt(inbuffer, encryptedbuffer, &key);
LPCSTR pszSource = (LPCSTR)encryptedbuffer;
DWORD nDestinationSize;
if (CryptBinaryToString(reinterpret_cast<const BYTE*> (pszSource), strlen(pszSource), CRYPT_STRING_BASE64, nullptr, &nDestinationSize))
{
LPTSTR pszDestination = static_cast<LPTSTR> (HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, nDestinationSize * sizeof(TCHAR)));
if (pszDestination)
{
if (CryptBinaryToString(reinterpret_cast<const BYTE*> (pszSource), strlen(pszSource), CRYPT_STRING_BASE64, pszDestination, &nDestinationSize))
{
printf("OUT: %s", pszDestination); // Base64 encoded output of encrypted string
}
}
}
This is my PHP code:
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('AES-256-CBC'));
$out = openssl_decrypt(base64_decode("G6g0f/K7EzAw8fxn9BTFzw=="), 'AES-256-CBC', "abc", OPENSSL_RAW_DATA, $iv);
echo $out;
There is no output from the PHP code.
The C++ code would output the following:
OUT: G6g0f/K7EzAw8fxn9BTFzw==
CBC mode, the mode used in the PHP code, does require an iv and it must be the same as the one used for encryption, also the modes must be the same for both encryption and decryption. For CBC mode you need to supply a block sized iv (16-bytes for AES). The encryption key also should to be the correct size.
The iv is not explicitly set in the C++ code and the mode and padding use the default, it is always better to expel;icitly specify all parameters. The iv should be random for each encryption.
You might consider RNCryptor, it is multi language and platform and handles all the details.