PHP AES Encryption into NodeJS using crypto module - javascript

My task is to follow the given and working PHP encryption into node.js. using any node module packages. I dont need to do the decryption because its already existing in their API and i just need to pass the encypted value to their API for decryption which is in PHP. I tried using node-crypto and seperate md5 module. here is the pseudo code:
Data Encryption Algorithm
Create a 16-byte random salt and hash. The salt is newly created every time you call
the API.
Hash the encryption key provided.
Encrypt using 'AES-128-CBC' and use the hashed salt value as vector and hashed
encryption key.
Prefix salt and append the encrypted data.
Do Base64 encoding.
JSON encode and post request
I think I'm almost done just few steps to get a successful response here is my current node.js code
Node:
const reqBody = {
"username": "jCpVyf3VEt",
"password": "eGD6TWKmnn",
"account_no": "0030300155398",
"tran_date": "08/06/2019 10:30:45",
"reference_no": "12328ALHYGZC20",
"area": "JENRA DAU"
};
const Serialize = require('php-serialize')
const md5 = require('md5');
//encrypt
const crypto = require('crypto'),
algorithm = 'aes-128-cbc',
key = 'IfZDGbVDHTxlJIkK',
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(data, key) {
let salt = crypto.randomBytes(16);
let hKey = md5(key);
let iv = md5(salt);
let serialized = Serialize.serialize(data);
let cipher = crypto.createCipheriv(algorithm, Buffer.from(hKey, 'hex'), Buffer.from(iv, 'hex'));
let crypted = cipher.update(serialized, inputEncoding, outputEncoding);
crypted += cipher.final(outputEncoding);
let encrypted = salt.toString('base64') + crypted.toString();
return encrypted;
}
encrypt(JSON.stringify(reqBody), key);
here is the working php code:
$data = json_encode([
'username' => "jCpVyf3VEt",
'password' => "eGD6TWKmnn",
'account_no' => "0030300155398",
'tran_date' => "08/06/2019 10:30:45",
'reference_no' => "12328ALHYGZC20",
'area' => "JENRA DAU"]);
function encrypt( $data) {
$key = md5("IfZDGbVDHTxlJIkK", true);
$cipher = "aes-128-cbc";
$salt = openssl_random_pseudo_bytes(16);
$iv = md5( $salt, true);
$encrypted_bin = $salt . openssl_encrypt( serialize( $data ), $cipher, $key, true, $iv);
$encrypted_str = base64_encode( $encrypted_bin);
return $encrypted_str;
}
echo encrypt($data);
for testing purpose here is the PHP code from their API for decryption:
$data = 'LI5BJJw1PEhWellnjKEt3g9oaHs8uDDknBT2qDNI7Rfs644+IjobOaFxlrIrOvDm7dkASRsOTu4Yuxzi4I5q29QoE5huH6y4/XZXsResZjLPidv1ToTnhB2UKXH5rX/g/8Od7ljO6VLVAS7zx+94xeOgtpP/idkkpDi1fRNGvnOkl1c6fcyVhwl2Pv+ijKSK9+ou+54dfQrCng2uBzKC6RrHY3lvP7ktsSvtnkXFqksrpjfJ2gnMH6sMIMzru1+D';
function decrypt($encrypted) {
$cipher = "aes-128-cbc";
$key = md5("IfZDGbVDHTxlJIkK", true);
$data = base64_decode($encrypted);
$salt = substr($data, 0, 16);
$iv = md5($salt, true);
$decrypted_bin = openssl_decrypt(substr($data, 16, strlen($data)), $cipher, $key, true, $iv);
if($decrypted_bin === false) {
return json_encode([ -102 => "Authentication Failed"]);
}
return unserialize( $decrypted_bin);
}
echo decrypt($data);
Running the PHP encryption code result a success response from the PHP decryption. But when I run my Node.js encryption I'm able to get an encrypted data but when I test the encrypted data from my Node.js and send the encrypted value into the PHP decryption code the result is authentication error. seems I'm not able to translate the PHP encryption algo into Node
.js

This is a very interesting one.. I think the main issue is the method of concatenating our salt and encrypted data in Node.js.
I found the following code worked nicely, it's giving us the same result as the PHP code.
Note that I'm using a fixed salt here (decoding from a fixed base64 string), since this gives us a deterministic output. You should probably switch to using crypto.randomBytes for this in production.
Also, PHP encodes "/" as "\/" in json output, see json_encode. This can be configured of course in PHP, (using the flag JSON_UNESCAPED_SLASHES in json_encode), but I suspect it can't be changed in your case. This is why I am replacing "/" with "\/" in the json plaintext.
const reqBody = {
"username": "jCpVyf3VEt",
"password": "eGD6TWKmnn",
"account_no": "0030300155398",
"tran_date": "08/06/2019 10:30:45",
"reference_no": "12328ALHYGZC20",
"area": "JENRA DAU"
};
const Serialize = require('php-serialize')
const md5 = require('md5');
//encrypt
const crypto = require('crypto'),
algorithm = 'aes-128-cbc',
key = 'IfZDGbVDHTxlJIkK',
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(input, key, salt) {
let serialized = Serialize.serialize(input);
let iv = md5(salt);
let hKey = md5(key);
let cipher = crypto.createCipheriv(algorithm, Buffer.from(hKey, 'hex'), Buffer.from(iv, 'hex'));
let crypted = cipher.update(serialized, inputEncoding);
let encrypted = Buffer.concat([salt, crypted, cipher.final()]);
return encrypted.toString(outputEncoding);
}
// We must escape forward slashes here, since this is how PHP will behave.
let data = JSON.stringify(reqBody).replace(/\//ig, "\\/");
// We can use any random salt here, e.g. crypto.randomBytes For this example we'll use the same as the existing encrypted data.
let salt = Buffer.from('LI5BJJw1PEhWellnjKEt3g==', 'base64');
console.log("Encrypted data: ", encrypt(data, key, salt));

Related

CryptoJS aes encrypt function PHP equivalent

I am trying to create a PHP equivalent to this JS code with CryptoJS:
function aesEncrypt (data) {
const key = 'GSTEGSTEjdfheyhdHSHSHSHDHHDHmdjjdn12ndndn5r=';
const iv = '\0';
const cipher = CryptoJS.AES.encrypt(data, CryptoJS.enc.Base64.parse(key), {
iv: CryptoJS.enc.Utf8.parse(iv), // parse the IV
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
})
return cipher.toString()
}
result of the js code : pHjpwiyKq7Rf4dFcBMbm1w==
here is the PHP code I wrote by reading other stackoverflow questions. But it did not return the same result.
$plaintext = "plainText";
$method = 'aes-256-cbc';
$key = base64_encode("GSTEGSTEjdfheyhdHSHSHSHDHHDHmdjjdn12ndndn5r=");
$iv = hex2bin('00000000000000000000000000000000');
$ciphertext = openssl_encrypt(
$plaintext,
$method,
$key,
OPENSSL_RAW_DATA,
$iv
);
$ciphertext = base64_encode($ciphertext);
echo $ciphertext;
result of the PHP code : +YJOMi2vISmEXIjUZls3MA==
In the PHP code, the key must be Base64 decoded and not Base64 encoded:
$key = base64_decode("GSTEGSTEjdfheyhdHSHSHSHDHHDHmdjjdn12ndndn5r=");
With this change the desired ciphertext is created.
Note that the ciphertext is Base64 encoded by default if 0 is passed instead of OPENSSL_RAW_DATA in the fourth parameter of the openssl_encrypt() call. The explicit Base64 encoding of the ciphertext is then not necessary.
Keep in mind that a static IV is insecure. Usually during encryption a random IV is generated, which is passed to the decrypting side along with the ciphertext (typically concatenated).

ScryptSync Key Cannot Be Used in createCipheriv, Nodejs, Crypto lib

I am new to topics related to encoding and I am having troubles with being able to convert my scryptsync key into something createCipheriv (Crypto library integrated into Nodejs) can use in the iv parameter.
const algorithm = 'aes-256-gcm';
var text = 'default'
var encrypted = secret;
class Auth
{
SignUp(pass)
{
console.log(pass);
var pair = ec.genKeyPair();
text = pair.getPrivate.toString('hex');
var key = crypto.scryptSync(pass, 'baethrowssalt', 32);
console.log(`The key is:${key}`); //this is not a string
key=key.toString('hex');
var cipher = crypto.createCipheriv(algorithm, key);
var encrypted = cipher.update(text, 'hex', 'hex') + cipher.final('hex');
fs.writeFileSync(file, encrypted);
return alert(`Close and reopen your app to integrate your wallet securely`);
}
as you can see above, I tried converting the scryptSync return to string hex so that createCipheriv is willing to use it, but I get the following error:
pass is passrig
passrig
The key is:�→A�r;yR�����▲�h�8��f�����v�A�,
TypeError [ERR_INVALID_ARG_TYPE]: The "iv" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received undefined
at Cipheriv.createCipherWithIV (internal/crypto/cipher.js:120:29)
at new Cipheriv (internal/crypto/cipher.js:227:22)
at Object.createCipheriv (crypto.js:117:10)
So my question contains two parts: how can I use scryptSync with the createCipheriv? To help me understand, what are the reasons behind your solutions?
I've created an example that encrypts some data using the algorithm aes-256-gcm and uses crypto.scryptSync to derive the key from a password.
The steps are as follows:
Derive our key from our password using crypto.scryptSync.
Create an IV using crypto.randomBytes.
Use our key and iv to encrypt the plaintext using our encrypt function.
Test our encrypted data by decrypting using our decrypt function.
This code is as follows:
const crypto = require("crypto");
const Algorithm = "aes-256-gcm";
function encrypt(plainText, key, iv) {
const cipher = crypto.createCipheriv(Algorithm, key, iv);
return { encrypted: Buffer.concat([cipher.update(plainText), cipher.final()]), authTag: cipher.getAuthTag() }
}
function decrypt(encrypted, key, iv, authTag) {
const decipher = crypto.createDecipheriv(Algorithm, key, iv).setAuthTag(authTag);
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
}
const password = "Speak Friend and Enter";
const plainText = "There is nothing either good or bad but thinking makes it so.";
const salt = crypto.randomBytes(32);
// Create an encryption key from our password, ensuring it is 32 bytes long - AES-256 needs a 256 bit (32 byte) key
const KEY = crypto.scryptSync(password, salt, 32);
const IV = crypto.randomBytes(16);
console.log("Key (derived from password, hex):", KEY.toString("hex"));
console.log("IV (hex):", IV.toString("hex"));
console.log("Plaintext:", plainText);
const { encrypted, authTag } = encrypt(plainText, KEY, IV);
console.log("Encrypted (hex):", encrypted.toString("hex"));
const decrypted = decrypt(encrypted, KEY, IV, authTag)
console.log("Decrypted:", decrypted.toString("utf-8"));

NodeJS Crypto TripleDes decryption (mcrypt port)

I am struggling with some legacy-code written in PHP 5.5 and mcrypt.
I want to create a backward-compatible functionality in Node.js so in the result I have to port code below to newer standards.
public function decr($hash) {
$decoded = base64_decode($hash);
$decodedShorter = substr($decoded, 0, -8);
$iv = substr($decoded, -8);
$decr = rtrim(#mcrypt_decrypt(MCRYPT_3DES, file_get_contents('some.key'), $decodedShorter, MCRYPT_MODE_CFB, $iv));
return $decr;
}
I've been experimenting with multiple strategies both crypto-js and native crypto out of node engine.
The latest problem I faced:
ERR_CRYPTO_INVALID_IV
const decrypt = (text, secretKey, iv = null) => {
const decipher = crypto.createDecipheriv('des-ede3-cfb8', secretKey, iv);
let decrypted = decipher.update(text, 'utf8');
decrypted += decipher.final();
return decrypted;
};
async function main() {
const decoded = atob(name);
const key = await readFile(
path.resolve(`some.key`)
)
const decodedShorter = decoded.substr(0, decoded.length - 8)
const iv = decoded.substr(-8)
return decrypt(decodedShorter, key, Buffer.from(iv))
}
Any ideas? Is the new openSSL implementation so different from mcrypt one that it is not compatible? Or maybe I messed up with something? I am pretty sure that types of arguments are correct as I was referring to #types/node/crypto, but there is something incorrect with content/logic itself...
The decr() method in the PHP code first Base64 decodes the encrypted data and then separates ciphertext and IV. Here the 8 bytes IV is expected to be appended to the ciphertext.
After that a decryption with AES in CFB mode is performed. There are different CFB variants of different segment sizes, here a segment size of 8 bits is used. CFB is a stream cipher mode, so no padding is needed/applied.
The bug in the posted NodeJS code is that ciphertext and IV are processed as strings using a UTF-8 encoding. This generally corrupts an arbitrary byte sequence (such as a ciphertext or an IV).
Regarding the ciphertext, the corruption happens in decipher.update(text, 'utf8'). Here UTF-8 is explicitly specified as input encoding in the second parameter. Regarding the IV, the corruption happens when reading the IV into the buffer: Buffer.from(iv). Since no encoding is specified in the second parameter, UTF-8 is implicitly used. Both problems can be fixed by using latin1 as encoding.
A more robust solution is to use buffers throughout, so that no encoding is necessary:
var crypto = require('crypto')
const decrypt = (text, secretKey, iv = null) => {
const decipher = crypto.createDecipheriv('des-ede3-cfb8', secretKey, iv);
let decrypted = decipher.update(text, '', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
const name = "OrjgCsq9EkT2TkCZzDOfW492nXQCNIC0BtVJy1FaaTXv2jXAPqx75kaUJVSG/5MCFXXq"
const decoded = Buffer.from(name, 'base64')
const decodedShorter = decoded.slice(0, decoded.length - 8)
const iv = decoded.slice(decoded.length - 8)
const key = Buffer.from('ffa3b5205582d6ea7de6439ec2bafef46a80810003158922', 'hex');
console.log(decrypt(decodedShorter, key, iv))
Test: Both codes decrypt the following ciphertext $ciphertext with the key $key into the given plaintext:
$ciphertext = 'OrjgCsq9EkT2TkCZzDOfW492nXQCNIC0BtVJy1FaaTXv2jXAPqx75kaUJVSG/5MCFXXq';
$key = hex2bin('ffa3b5205582d6ea7de6439ec2bafef46a80810003158922');
// Plaintext: The quick brown fox jumps over the lazy dog

AES Encryption in PHP and Decryption in Javascript

I have an application in which I am encrypting a json encoded array using AES CBC 128 algorithm and then Decrypting it in javascript(React/Next Js Project). My Encryption in php is as shown in the below code
ENCRYPTION PHP
$plaintext = "message to be encrypted";
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
$ciphertext = base64_encode( $iv.$hmac.$ciphertext_raw );
I am facing problems in decryption of this in Javascript
my code so far is as shown below
const baseenc = CryptoJS.enc.Base64.parse(cipher).toString();
var encrypted = CryptoJS.AES.decrypt(cipher, key, { iv: iv }).toString();
var plaintext = CryptoJS.enc.Latin1.stringify(encrypted);
Can any body please show what is the error or help me in getting the correct output
The following steps must be implemented in the CryptoJS code:
Separate IV, HMAC and ciphertext (after Base64 decoding)
Calculate the HMAC for the ciphertext
Check the authenticity of the ciphertext. The ciphertext is authentic if the received and calculated HMAC are identical.
Perform decryption, only if the ciphertext is authentic
The following code is a possible implementation. As key 0123456789012345 was applied and with the PHP code the used ciphertext was generated:
var ciphertext = 'WqfMfCxKg7U7h5S1mbx7mSHOkkkIrUUpg++mX4ZdWt0I26VfKn7bsi60Oo/SIsWQGyC4dF5z081NvjTXwZGjIpguA0k/QqIM/GDEpCojaro=';
var key = '0123456789012345';
// Convert key and ciphertext into WordArrays
var ciphertextWA = CryptoJS.enc.Base64.parse(ciphertext);
var keyWA = CryptoJS.enc.Utf8.parse(key);
// Separate IV, HMAC and ciphertext
var ivWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(0, 4));
var hmacWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(4, 4 + 8));
var actualCiphertextWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(4 + 8));
// Authenticate
var hmacCalculatedWA = CryptoJS.HmacSHA256(actualCiphertextWA, keyWA);
if(CryptoJS.enc.Base64.stringify(hmacCalculatedWA) === CryptoJS.enc.Base64.stringify(hmacWA)) {
// Decrypt if authentication is successfull
var decryptedMessageWA = CryptoJS.AES.decrypt({ciphertext: actualCiphertextWA}, keyWA, {iv: ivWA});
var decryptedMessage = CryptoJS.enc.Utf8.stringify(decryptedMessageWA);
console.log(decryptedMessage);
} else {
console.log('Authentication failed!');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Please note that it is better to use different keys for encryption and authentication, see here.

AES Encryption with CryptoJS and PHP

I want to implement AES encryption using JavaScript. Used AES CBC Mode. I have managed to do it in PHP. It looks like:
public function encrypt($value) {
if (empty($value)) {
return $value;
}
$value = Unicode::convertToUtf8($value, 'UTF-8');
if ($key = $this->getEncryptionKey()) {
// Generates from key 1st 16 bytes.
$iv = mb_substr($key, 0, 16);
//encrypt message with key
$message = openssl_encrypt($value, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($message);
}
}
public function getEncryptionKey() {
$key = 'secret';
$key = Unicode::convertToUtf8($key, 'UTF-8');
// Make sure the key is the correct size.
if (strlen($key) < 32) {
$key = str_pad($key, 32, "\0");
}
if (strlen($key) > 32) {
$key = mb_substr($key, 0, 32);
}
return $key;
}
If I give $value = retest2; it gives me ukUCH0SvgdmM8vTqQumAVg== output
I know it's right, I tried it using C# as well got the same result. But when I try to replicate this using JavaScript, I wasn't able to produce same PHP's output. Below is the javascript code that I have tried:
const message = utf8.encode('retest2');
const password = utf8.encode('secret').padEnd(32, '\0');
const key = CryptoJS.enc.Hex.parse(password);
const iv = CryptoJS.enc.Hex.parse(password.substring(0, 16));
const encrypted = CryptoJS.AES.encrypt(message, key, {
iv: iv
});
console.log(btoa(encrypted.toString()));
Using the same value I get dzd4bjNwenduQT09.
I have also read other similar questions asked on the same topic here, but I can't seem to figure out where I am going wrong?
Thanks!
As #symcbean said,
You shouldn't be using the key or data derived from it as your
initialization vector.
I assume, you have no option and you have to use key or data derived from it as your initialization vector.
A few months ago, I had the exact same situation and I did something like this,
const message = 'retest2';
let password = 'secret';
if (password.length < 32) {
password = password.padEnd(32, '\0');
}
const iv = CryptoJS.enc.Utf8.parse(password.substring(0, 16));
password = CryptoJS.enc.Utf8.parse(password);
const encrypted = CryptoJS.AES.encrypt((message), (password), {
iv: iv
});
console.log(CryptoJS.enc.Base64.stringify(encrypted.ciphertext));
These should be comments but space is limited....
You shouldn't be using the key or data derived from it as your initialization vector.
I know it's right, I tried it using C# as well
You should have shown us the code.
Your openssl call invokes the key derivation function to create a (better?) encryption key from the data you pass in the third parameter. OTOH, in crypto.js there is no implicit key derivation.

Categories