PHP Convert Simple 3 Line JavaScript to PHP Equivalent - javascript

I've been provided with a few lines of JavaScript that I need to convert to the PHP equivalent. Not being a JavaScript developer I'm struggling with this. Here's the JavaScript sample I've been provided:
const crypto = require('crypto')
const secret_in_hex = Buffer.from(secret, 'hex');
const hash = crypto.createHmac('sha512', secret_in_hex)
.update(body)
.digest('hex')
// Compare hash with the received X-Onfleet-Signature in raw bytes
The docs for the API I'm working with to setup a Webhook receiver in PHP mention:
Each webhook request contains a signature from Onfleet in X-Onfleet-Signature header. To authenticate the webhook request received on your webhook server, you will need to validate against this header. To validate against X-Onfleet-Signature, you will need to compare its value with an HMAC you have generated using the hexadecimal format of your webhook secrets and the full body of the webhook POST request in raw bytes.
I'm assuming I'll be using the hash_hmac function and possibly the bin2hex function but completely stumped at this point and appreciate if someone can show me the PHP equivalent of the above JavaScript (assuming there is one).

The simplest equivalent should be:
$secretInHex = hex2bin($secret);
$hash = hash_hmac('sha512', $body, $secretInHex);
You should note that hex2bin function does NOT convert a hexadecimal number to a binary number but it decodes a hexadecimally encoded binary string.
So the secret provided should already be a hexadecimal else hex2bin will throw an exception

Related

openssl_decrypt PHP to CryptoJS

In PHP I use openssl_decrypt($encryptData,'AES128',$key, OPENSSL_ZERO_PADDING|OPENSSL_RAW_DATA) and I need the exact same thing with same option on JS so I use CryptoJS with this function cryptoJS.AES.decrypt(encryptData, key, {padding: cryptoJS.pad.ZeroPadding}).toString().
With PHP my decoded message start with C7 with others hexa character. But in JS only decimal values.
Here encryptData and key on PHP and JS have the exact same value. But cryptoJS return something different from PHP.
I don't know what can be the problem. I've tried to change the base of my values with UTF8 or Base64. But nothing works.
How can I convert this function to crypto and have it working ?
Thank you !
PHP example :
$encryptData = hex2bin("D5F630E93F36C21293012D78E5A384F1");
$key = hex2bin("A254FE00A791AA74386E8DEF3712B256");
var_dump(bin2hex(openssl_decrypt($encryptData,'AES128', $key, OPENSSL_ZERO_PADDING|OPENSSL_RAW_DATA)));
//result : c704469332aa61804601008a92dc10e5
JS example : (and of course hex2bin return the same output than in PHP)
let encData = hex2bin("D5F630E93F36C21293012D78E5A384F1");
let key = hex2bin("A254FE00A791AA74386E8DEF3712B256");
let data = bin2hex(cryptoJS.AES.decrypt(encData, key, {padding: cryptoJS.pad.ZeroPadding}).toString());
console.log(data);
//result : 326638346632336661323135353832343236376139343564
Result change every time I reload on JS.
CryptoJS uses the WordArray type and provides encoders for conversion, e.g. the Hex encoder.
In the PHP code, aes128 is applied, which is an alias for aes-128-cbc and requires an IV. If none is specified, PHP implicitly uses a zero vector, which must be explicitly specified in the CryptoJS code. The CBC mode itself does not need to be explicitly specified, since it is the default.
Moreover the PHP code disables padding, which must be explicitly specified in the CryptoJS code, since PKCS7 padding is the default. Note that OPENSSL_ZERO_PADDING does not enable Zero padding, but disables padding, i.e. the CryptoJS counterpart is NoPadding.
Your CryptoJS code must be changed as follows to be functionally equivalent to the PHP code:
let encData = CryptoJS.enc.Hex.parse("D5F630E93F36C21293012D78E5A384F1");
let key = CryptoJS.enc.Hex.parse("A254FE00A791AA74386E8DEF3712B256");
let iv = CryptoJS.enc.Hex.parse("00000000000000000000000000000000");
let data = CryptoJS.AES.decrypt(
{ciphertext: encData},
key,
{iv: iv, padding: CryptoJS.pad.NoPadding}
).toString(CryptoJS.enc.Hex);
console.log(data); // c704469332aa61804601008a92dc10e5
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Please also note that a static IV is insecure. Typically, a random IV is generated for each encryption, which is not secret and passed along with the ciphertext (usually concatenated) to the recipient, who separates both before decryption.

Crypto-js local side to php server side

I have an app that use crypto-Js AES. The simulate working code is:
var ciphertext = CryptoJS.AES.encrypt('My_message', 'My_secret_key');
console.log(ciphertext.toString());
the answer is:
U2FsdGVkX1/Dd3uAr/mdw5lVoBvq0UX5LHnNoX24JAM=
when I try to reproduce it server side I never get the same answer:
$passphrase='My_secret_key';
$value='My_message';
$salt = openssl_random_pseudo_bytes(8);
$salt ='';
$salted = '';
$dx = '';
while (strlen($salted) < 48) {
$dx = md5($dx.$passphrase.$salt, true);
$salted .= $dx;
}
$key = substr($salted, 0, 32);
$iv = substr($salted, 32,16);
$encrypted_data = openssl_encrypt($value, 'aes-256-cbc', $key, true, $iv);
echo base64_encode($encrypted_data);
server side answer:
3jSTl1yR55lfTbz7f0o3Yw==
I must miss something but can't point out what. Local side can't touched.
All help is welcome
If the second parameter in CryptoJS.AES.encrypt is passed as string, it is interpreted as a passphrase from which the actual key and IV are derived, [1]. This is achieved by using the functionality of the OpenSSL-function EVP_BytesToKey with an iteration count of 1 and the MD5-digest, [2] [3] (note that CryptoJS doesn't consider the switch of the default digest from MD5 to SHA256 from OpenSSL version 1.1.0c on, [4]).
CryptoJS.AES.encrypt returns a CipherParams-object that encapsulates ciphertext, key, IV, and salt, [5]. In addition CipherParams#toString() returns the result in OpenSSL-format as Base64-encoded string. The OpenSSL-format consists of a 16-byte header and the subsequent ciphertext. The header starts with the ASCII-encoded string Salted__ followed by an 8-byte salt. The salt is randomly generated each time and used together with the password to derive the key / IV. This creates a different key / IV each time.
The PHP-code is functionally identical: Key and IV are derived with an analog logic from a passphrase using a freshly generated salt each time (for the proof, see below). However, some minor changes are necessary:
The following line must be removed: $salt ='';
In the current code, only the Base64-encoded ciphertext is displayed. For a Base64-encoded output of the result in OpenSSL-format the code must be instead:
echo base64_encode('Salted__'.$salt.$encrypted_data);
The 4th parameter in openssl_encrypt should be changed from true to OPENSSL_RAW_DATA. Both are functionally identical, but the use of OPENSSL_RAW_DATA is more transparent.
The JavaScript- and PHP-code generate a new salt each time and thus a different key and IV, which changes the ciphertext each time. That's the way it should be. Since the salt is stored together with the ciphertext, it is possible to decrypt the ciphertext at any time using the passphrase.
Proof that both codes use the same logic to derive key and IV: The new salt / ciphertext generated each time prevents a direct comparison of the results of both codes. In order to perform this comparison without much effort, it is best to use the salt generated in the JavaScript-code in the PHP-code as well. The salt in the JavaScript-code can be determined as hexadecimal string with:
console.log(ciphertext.salt.toString(CryptoJS.enc.Hex));
This salt is to be used in the PHP-code instead of the randomly generated salt (of course only for this one comparison):
$salt = hex2bin('<Salt from JavaScript-Code as hexadecimal string>');
A comparison of both outputs now proves that they are equal, showing that both codes are functionally identical.
I've been in your exact situation before. We had extreme difficulty getting the same results on PHP and Java (for Android). Many developers at 2 companies over many days. No luck.
We eventually ended up calling CryptoJS from PHP. If I remember correctly, there is something non-standard in Crypto JS. I could be wrong it was a while back.
Calling CryptoJS with node through PHP can be a more robust solution. Then you can use the same library which ensures compatibility.
$result = shell_exec('node yourCryptoJSprogram.js');
We did reach a limit to how much data can be passed as arguments this way. I would recommend writing to a file with PHP and reading with NodeJS again.
If performance becomes problematic, consider running an Express server and making REST calls from PHP.
If that answer doesn't satisfy you, consider using or copying this simple OpenSSL based PHP library I wrote to figure it out:
https://github.com/io-digital/php-crypto

Anyone gotten CryptoJS to work with Cybersource REST API v3 reporting for the Signature?

Unable to generate correct HMAC 256 hash for Cybersource Signature headers
I have been working on this for a couple of days, and I can easily get it to work within .Net using the supplied sample code to generate the correct HMAC Signature. However I cannot get CryptoJS to work, I beleive it stems from the fact that CryptoJS is interpreting the "\n" as a LRCF internally and thus throws the encryption off. Please be aware that I am limited to ECMA5 and have brought the CryptoJS in as a minified function.
var data = "host: api.cybersource.com\ndate: Mon, 10 Jun 2019 20:41:05 GMT\n(request-target): get /reporting/v3/report-downloads?organizationId={OrgId}&reportDate=2019-06-06&reportName=PaymentBatchDetailReport\nv-c-merchant-id: {MerchId}";
var hash = CryptoJS.HmacSHA256(data, "{SecretKey}");
var base64 = CryptoJS.enc.Base64.stringify(hash);
document.write(base64);
Any help would be greatly appreciated!
Make sure to Base64 decode your secret key before passing it to CryptoJS.HmacSHA256.
For example
var words = CryptoJS.enc.Base64.parse({SecretKey});
var hash = CryptoJS.HmacSHA256(data, words);

JavaScript. WebSocket server. Error during WebSocket handshake: Incorrect 'Sec-WebSocket-Accept' header value [duplicate]

I'm following rfc6455:
Concretely, if as in the example above, the |Sec-WebSocket-Key|
header field had the value "dGhlIHNhbXBsZSBub25jZQ==", the server
would concatenate the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
to form the string "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-
C5AB0DC85B11". The server would then take the SHA-1 hash of this,
giving the value 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6
0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea. This value is
then base64-encoded (see Section 4 of [RFC4648]), to give the value
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=". This value would then be echoed in
the |Sec-WebSocket-Accept| header field.
and fail to generate the correct "Sec-WebSocket-Accept".
In order to understand the process I'm using online SHA1 hash and Base64 Encode.
The online SHA1 hash for "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11" give the correct result: "b37a4f2cc0624f1690f64606cf385945b2bec4ea" as described in rfc6455.
But The online Base64 Encode give me the wrong results "YjM3YTRmMmNjMDYyNGYxNjkwZjY0NjA2Y2YzODU5NDViMmJlYzRlYQ==" for input "b37a4f2cc0624f1690f64606cf385945b2bec4ea".
The result should be "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
What am I doing wrong?
You need to base64-encode the raw sha1 digest.
You are encoding the hexadecimal string representation of the digest which is double the length.
Online tools work with text and don't work with raw binary data, that's why you are getting wrong results.
Python 2 example:
import hashlib, base64
h = hashlib.sha1("dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
print "hexdigest:", h.hexdigest() # hexadecimal string representation of the digest
print "digest:", h.digest() # raw binary digest
print
print "wrong result:", base64.b64encode(h.hexdigest())
print "right result:", base64.b64encode(h.digest())
This prints:
hexdigest: b37a4f2cc0624f1690f64606cf385945b2bec4ea
digest: ᄈzO,ÀbOミöFÏ8YEᄇᄒÄê
wrong result: YjM3YTRmMmNjMDYyNGYxNjkwZjY0NjA2Y2YzODU5NDViMmJlYzRlYQ==
right result: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Python 3 example:
import hashlib, base64
h = hashlib.sha1(b"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
print("hexdigest:", h.hexdigest()) # hexadecimal string representation of the digest
print("digest:", h.digest()) # raw binary digest
print()
print("wrong result:", base64.b64encode(h.hexdigest().encode()).decode())
print("right result:", base64.b64encode(h.digest()).decode())
This prints:
hexdigest: b37a4f2cc0624f1690f64606cf385945b2bec4ea
digest: b'\xb3zO,\xc0bO\x16\x90\xf6F\x06\xcf8YE\xb2\xbe\xc4\xea'
wrong result: YjM3YTRmMmNjMDYyNGYxNjkwZjY0NjA2Y2YzODU5NDViMmJlYzRlYQ==
right result: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Encryption & Encoding(AES, UTF-8 and base64)code conversion from JAVA to node.js giving different results [duplicate]

This question already has an answer here:
AES encryption in Node.js to match expected decryption in Python
(1 answer)
Closed 6 years ago.
Update : I finally managed to recreate the whole Java code as required for the third party service. I must add that some of the libraries used are deprecated but I cannot do anything because that is what the other side is using and I must comply.
Java Code
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),
"AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(1, secretKeySpec);
byte[] aBytes = cipher.doFinal(inputString.getBytes());
Input Key : xxxxxxxxyyyyyyyy
Input Text: maryhadalittlelamb
Output :
Z22GETg3Anl92%2BoyqdVWs9haQveaZxkDn8sQYP08iCY%3D
node.js Code
var cipher = crypto.createCipher('aes-128-ecb', key);
var encryptedPassword = cipher.update(text, 'utf8', 'base64');
encryptedPassword += cipher.final('base64');
console.log(encryptedPassword);
Input Key : xxxxxxxxyyyyyyyy
Input Text: maryhadalittlelamb
Output: mnqrpA2eqAhmseTrkBtH3YSGMoFs+ECPUamVd8/bgAQ=
The output for same inputstring and key is different for both. In fact the node.js is different but the base64 one looks identical nevertheless.
I am fairly new to these things therefore I have lost my may.
In node.js you base64 encode the input string before you encrypt, it needs to be the output from the encrypt that needs to be base64 encoded.
Also, you need a call to cipher.final(..) after cipher.update(..) to finish off the encryption operation. Remember to capture the output from both.
In addition to this please note that ECB mode is insecure.

Categories

Resources