How do I replicate this PHP hashing implementation in Node.js? - javascript

I'm attempting to recreate a password hashing implementation in node.js (LTS latest--14.x) that was originally written in PHP (7.2). I believe the node.js implementation that I've written should do the exact same thing; however, the node.js implementation differs after the first pass of the hash in the loop. What am I missing here?
PHP implementation (I cannot change this since it's part of a web framework and existing authentication relies on the hashing mechanism staying the same):
$algo = "sha512";
$salt = "someSalt";
$password = 'somePassword';
$count = 32768;
$hash = hash($algo, $salt . $password, TRUE);
// $hash is the same as in the corresponding line in the node.js implementation
do {
$hash = hash($algo, $hash . $password, TRUE);
// $hash differs from the node.js implementation after the first pass here... why?
} while (--$count);
Node.js implementation:
const crypto = require('crypto');
const algorithm = 'sha512';
const salt = 'someSalt';
const password = 'somePassword';
let count = 32768;
let hash = crypto
.createHash(algorithm)
.update(salt + password)
.digest('binary');
// hash is the same as in the PHP implementation here
do {
hash = crypto.createHash(algorithm).update(hash + password).digest('binary');
// hash differs between the two implementations after the first pass here... why?
} while (--count);
EDIT: Updated to show the original Node.js implementation where I did not stringify data being passed to update().

Expanding on what my comment was I think the following might work.
NODE:
const crypto = require('crypto');
const algorithm = 'sha512';
const salt = 'someSalt';
const password = 'somePassword';
let count = 32768;
let hash = crypto
.createHash(algorithm)
.update(String(salt) + String(password))
.digest('hex');
// hash is the same as in the PHP implementation here
do {
hash = crypto.createHash(algorithm).update(String(hash) + String(password)).digest('hex');
// hash differs between the two implementations after the first pass here... why?
} while (--count);
console.log(hash);
PHP code:
$algo = "sha512";
$salt = "someSalt";
$password = 'somePassword';
$count = 32768;
$hash = bin2hex(hash($algo, $salt . $password, TRUE));
// $hash is the same as in the corresponding line in the node.js implementation
do {
$hash = bin2hex(hash($algo, $hash . $password, TRUE));
// $hash differs from the node.js implementation after the first pass here... why?
} while (--$count);
var_dump($hash);
Confirmed my findings with the output from NODE:
node nodeTest.js
df8202221e5cbff38c16a33945efa8dcb44d0e7267cdf1514cefffb3df321f69ad1d9b01cfb6360391f1de4791e26a179fd165248b4b75699cb2d3395c971351
PHP output:
php test.php
string(128) "df8202221e5cbff38c16a33945efa8dcb44d0e7267cdf1514cefffb3df321f69ad1d9b01cfb6360391f1de4791e26a179fd165248b4b75699cb2d3395c971351"

I had to pass 'binary' to update() while hashing in the loop, since the data being provided is in binary.
// The data being passed to update() here is string (update defaults to utf-8, I believe).
let hash = crypto
.createHash(algorithm)
.update(salt + password)
.digest('binary');
do {
// The data being passed to update() is binary.
hash = crypto.createHash(algorithm).update(hash + password, 'binary').digest('binary');
} while (--count);
Credit: #Topaco for the finding the solution to my problem!

Related

Shopify verification failed

I am trying to create a public app in Shopify. I can set the scopes required our app. During installation it asks for shopify verification. I have referred this document https://shopify.dev/tutorials/authenticate-with-oauth#verification. I have passed message as code=%code-from-shopify%&shop=%shop.myshopify.com%&state=%state-from-shopify%&timestamp=%timestamp-from-shopify% with secret as Shopify app's secret key but it never match with hmac present in url as a parameter.
I have created an app 3 years ago, it is working fine as I mentioned above but when I created an app 9 days ago is not working and the secret key for this new app is starting as shpss_
How do I fix this?
First thing please check your SHOPIFY_KEY and SHOPIFY_SECRET is this correct and are you using the same in your code.
Once you redirect from shopify to your given call backUrl you will get hmac and code from request.
Here is the code to get Access Token from hmac
foreach ($_REQUEST as $key => $value) {
if ($key !== "hmac" && $key != "signature") {
$hashArray[] = $key . "=" . $value;
}
}
$hashString = implode($hashArray, "&");
$hashedString = hash_hmac("sha256", $hashString, 'YOUR_SHOPIFY_SECRET');
/* compare resulting hashed string with hmac parameter */
if ($_REQUEST['hmac'] !== $hashedString) {
return 403;
}
$shopUrl = "https://" . $_REQUEST["shop"] . "/admin/oauth/access_token.json";
$postData = http_build_query(
array(
"client_id" => 'YOUR_SHOPIFY_KEY',
"client_secret" => 'YOUR_SHOPIFY_SECRET',
"code" => $_REQUEST["code"],
)
);
/* Call using curl post you will get Access token in JSON */
$result = shop_auth_curl_request_call($shopUrl, $postData);
$tokenResponse = json_decode($result, true);
$tokenResponse['access_token'] // In JSON you will get access token

Data not being sent via XMLHttpRequest

I have a weird issue with a script I am using to send some info to my PHP. I say weird because this code is a direct copy of some already working code on the same application, with new variable names. I need to upload a small amount of data to my server, and while the value is there and the code runs all the way through, the POST data is not being sent or something. I have checked, and it says it has been submitted to my PHP with the value but nothing happens. I am using Vue.js if there are questions about some of my formatting
I have tried looking at other examples online, but my thing is that this was a block of code that copied from a working part of my application. It works until the data transfer from JS -- PHP
JS
editDisplayName: function() {
var self = this;
var newName = prompt("10 Characters Max, 3 Min", "Enter Display Name");
if(newName.length <= 10 && newName.length >= 3 ) {
var sendData = new XMLHttpRequest();
sendData.open("POST", "primary.php", true);
sendData.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
sendData.send(newName);
self.username = window.localStorage.getItem('username');
window.localStorage.clear();
}
}
PHP
function changeUsername() {
// Connect to Server
$link = // Commented Out For Security;
if (mysqli_connect_error()) {
die ("DB Connection Error");
}
$newName = $_POST['newName'];
$newName = mysqli_real_escape_string($link, $newName);
$token = $_COOKIE['validToken'];
$token = mysqli_real_escape_string($link, $token);
$query = "UPDATE userData SET username = '$newName' WHERE token = '$token' LIMIT 1";
mysqli_query($link, $query);
mysqli_close($link);
echo("
<script>
var username = $newName;
window.localStorage.setItem('username', username);
</script>
");
}
if(isset($_POST['newName'])) {
changeUsername();
}
I'm expecting on my DOM that I would have the new username set, instead it is blank. The spot where stuff stops working is somewhere with the POST data being sent, since my PHP isn't picking anything up, but I can't figure out what is wrong.
Instead of just sending the value, you should send the parameter name as well.
So, instead of using sendData.send(newName);, try using:
sendData.send("newName=" + newName);
(as your Content-type is application/x-www-form-urlencoded)
Or if you want to use JSON:
Change your Content-type to application/json, then send the data as a JSON string, like so:
sendData.send(JSON.stringify({'newName': newName}));

encryption in PHP and decryption in node.js

I've tried suggestions and tricks by reading various answers on stack overflow but they don't seem to be sufficient or maybe there's something basic i'm missing. Basically i'm trying to encrypt a value in php and pass it to the webpage from where it's read by JavaScript and send to node server for processing. But i'm unable to get the same value back on node server which i encrypted in php.
Below is the php code and php version is 5.5.12 running on windows 7 64 bit:-
function encrypt($string){
$key = hash("SHA256", '1d417e2ffb2a00a3', true);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$padding = $blockSize - (strlen($string) % $blockSize);
$string .= str_repeat(chr($padding), $padding);
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key,$string, MCRYPT_MODE_CBC, $iv);
$result['cipher'] = base64_encode($ciphertext);
$result['iv'] = base64_encode($iv);
return $result;
}
My node.js version is 0.10.31 running on windows 7 64 bit and code is below:-
var express = require("express");
var Server = require("http").Server;
var cookie = require("cookie");
var app = express();
var server = Server(app);
var sio = require("socket.io")(server);
var crypto = require('crypto');
sio.sockets.on("connection", function(socket) {
try{
socket.on('incoming_data', function(data){
var txt = new Buffer(data.encrypted_text,'base64');
var key = new Buffer('1d417e2ffb2a00a3','utf8');
var iv = new Buffer(data.iv,'base64');
var decipher = crypto.createDecipheriv('aes-128-cbc',key,iv);
var chunks = [];
chunks.push(decipher.update(txt,'hex','binary'));
chunks.push(decipher.final('binary'));
var fuid = chunks.join('');
console.log(fuid);
});
}catch(e){
console.log("err:-"+e);
console.log(e);
}
});// on connection ends
server.listen(9267, function(){
console.log('Node server listening on *:9267');
});
process.on('uncaughtException', function(err) {
console.log("FATAL: "+new Date().getTime()+": "+err);
});
The error i get from printing fuid in nodejs console is as below:-
FATAL: 1414483246855: TypeError: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
I am looking for solutions to the following in the answers:-
1) Problems with my code and what needs to be fixed to get back the same value on node server as a string.
2) Would like to concatenate the encrypted text and the iv and send them both as a single base64 encoded string to the server. So would like the code that will be needed to separate them back on node server ready to be passed to the crypto module.
3) This code seems vulnerable to oracle padding attacks. It will be great if you can suggest how can i make it secure.
Thanks
The problem might be with your encoding:
chunks.push(decipher.update(txt,'hex','binary'));
hex looks strange, since your inputs are buffers, not strings.
The following quick test works (also answers 2.):
php:
$key = 'secretsecretsecr';
$string = 'attack at dawn';
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$padding = $blockSize - (strlen($string) % $blockSize);
$string .= str_repeat(chr($padding), $padding);
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key,$string, MCRYPT_MODE_CBC, $iv);
$packet = chr($iv_size) . $iv . $ciphertext;
print base64_encode($packet);
node:
var crypto = require('crypto');
var key = 'secretsecretsecr';
var data = 'paste what the php code printed';
var txt = new Buffer(data,'base64');
var iv_size = txt[0];
var iv = txt.slice(1, iv_size + 1);
var ct = txt.slice(iv_size + 1);
var decipher = crypto.createDecipheriv('aes-128-cbc',key,iv);
var chunks = [];
chunks.push(decipher.update(ct));
chunks.push(decipher.final());
console.log(chunks.join(''));
The passes the iv size along, you can also simply hardcode it on the node side.
Regarding 3), I am by no means an expert, from what I've read the workaround is to sign your encrypted packets with HMAC to make sure they're coming from your application and not from the "oracle" (http://www.ietf.org/id/draft-mcgrew-aead-aes-cbc-hmac-sha2-05.txt).

Encrypt with Cryptico.js, Decrypt with OpenSSL

I am creating a public/private key on the server, sending the key to the JavaScript client where it encrypts a users password. The client sends the password to the server, and the server uses the private key to decrypt it, but the password is coming back null. I have verified all values supporting the situation are correct, so it's something with the encryption/decryption specifically. Where am I going wrong?
Possibly, is cryptico.js not compatible with php openssl?
Library Info:
https://github.com/wwwtyro/cryptico
http://www.php.net/manual/en/function.openssl-pkey-new.php
Here are relevant code snippets:
PHP - create public/private key
$config = array(
"digest_alg" => "sha512",
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
);
// Create the private and public key
$res = openssl_pkey_new($config);
// Extract the private key from $res to $privateKey
openssl_pkey_export($res, $privateKey);
// Extract the public key from $res to $publicKey
$publicKey = openssl_pkey_get_details($res);
$publicKey = $publicKey["key"];
JavaScript - Client encrypts data with public key.
var xhr = new XMLHttpRequest();
var data = new FormData();
xhr.open('POST', '/signUp2.php');
data.append('user', User);
var encryptedPassword = cryptico.encrypt(password, localStorage["publicKey"]);
data.append('password', encryptedPassword.cipher);
xhr.onreadystatechange = function()
{
if(xhr.readyState == 4 && xhr.status == 200)
{
var jsonArray = JSON.parse(xhr.responseText);
if(jsonArray[0] == "0")
{
alert("Account created. You may now sign in.");
}
else
alert("Error Code: " + jsonArray[0]);
}
}
xhr.send(data);
PHP - Server recieves encrypted password and attemps to decrypt unsuccessfully
openssl_private_decrypt($encryptedPassword, $decryptedPassword, $row[1]);
cryptico.js could work with openssl, but we have to modify it a bit.
it don't directly recognize the public key in pem format (which openssl use). we have to extract the 'n' and 'e' part of a public key in php side:
$key = openssl_pkey_new(array(
'private_key_bits' => 1024,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
'digest_alg' => 'sha256'
));
$detail = openssl_pkey_get_details($key);
$n = base64_encode($detail['rsa']['n']);
$e = bin2hex($detail['rsa']['e']);
also, cryptico.js hardcoded the 'e' part of a public key (see definition of publicKeyFromString in api.js), so we need to fix this:
my.publicKeyFromString = function(string)
{
var tokens = string.split("|");
var N = my.b64to16(tokens[0]);
var E = tokens.length > 1 ? tokens[1] : "03";
var rsa = new RSAKey();
rsa.setPublic(N, E);
return rsa
}
now we are able to encrypt strings:
var publicKey = "{$n}|{$e}",
encrypted = cryptico.encrypt("plain text", publicKey);
job is not finished yet. the result of cryptico.encrypt is NOT simply encrypted by RSA. indeed, it was combined of two parts: an aes key encrypted by RSA, and the cipher of the plain text encrypted with that aes key by AES. if we only need RSA, we could modify my.encrypt:
my.encrypt = function(plaintext, publickeystring, signingkey)
{
var cipherblock = "";
try
{
var publickey = my.publicKeyFromString(publickeystring);
cipherblock += my.b16to64(publickey.encrypt(plaintext));
}
catch(err)
{
return {status: "Invalid public key"};
}
return {status: "success", cipher: cipherblock};
}
now we are able to decrypt the cipher with openssl:
$private = openssl_pkey_get_private("YOUR PRIVATE KEY STRING IN PEM");
// $encrypted is the result of cryptico.encrypt() in javascript side
openssl_private_decrypt(base64_decode($encrypted), $decrypted, $private);
// now $decrypted holds the decrypted plain text

Creating a signed S3 URL with Javascript

I am attempting to create a signed S3 URL using Javascript & NodeJS. I have used this
specification.
var crypto = require('crypto'),
date = 1331290899,
resource = '/myfile.txt',
awskey = "XXXX",
awssecret = "XXXX";
var stringToSign ='GET\n\n\n' + date + '\n\n' + resource;
var sig = encodeURIComponent(crypto.createHmac('sha1', awssecret).update(stringToSign ).digest('base64'));
var url = "https://s3-eu-west-1.amazonaws.com/mybucket" +
resource + "?AWSAccessKeyId=" + awskey + "&Expires="+ date +
"&Signature="+ sig
This creates a url similar to this:
https://s3-eu-west-1.amazonaws.com/mybucket/test.txt?AWSAccessKeyId=XXXXXX&Expires=1331290899&Signature=EciGxdQ1uOqgFDCRon4vPqTiCLc%3D
However, I receive the following error when accessing it:
SignatureDoesNotMatch
The request signature we calculated does not match the signature you provided.
Check your key and signing method.
What am I doing wrong when creating the signature?
EDIT - ATTEMPT WITH KNOX
I am now attempting to use Knox to produce a signed URL. I need to add headers with the request to force download. I have edited the following:
Added amazonHeaders: 'response-content-disposition:attachment', to client.signedUrl- http://jsfiddle.net/BpGNM/1/
Added options.amazonHeaders + '\n' + to auth.queryStringToSign - http://jsfiddle.net/6b8Tm/
The message that is now being sent to auth.hmacSha1 to create the the sig is:
'GET\n\n\n1321374212\nresponse-content-disposition:attachment\n/meshmesh-dev/test/Readme.md'
I have then tried to access my new URL with the response-content-disposition=attachment added as GET var. However, I am still receiving the same error stated above.
I would try using Knox along with Node.Js . Its known to be a great combination and also itself utilizes the Node.JS Crypto library which is kind of what you're trying to do - saving you time:)
More info here : https://github.com/LearnBoost/knox
Than, you could just do something like:
var knox = require('knox');
var s3Client = knox.createClient({
key: 'XXX',
secret: 'XXX',
bucket: 'XXX'
});
var expires = new Date();
expires.setMinutes(expires.getMinutes() + 30);
var url = s3Client.signedUrl(filename, expires);
Edit:
You could also look into Knox and just check what the signedUrl function does and implement that yourself.Than you could add to the auth.signQuery call an extra option called amazonHeaders:
Client.prototype.signedUrl = function(filename, expiration){
var epoch = Math.floor(expiration.getTime()/1000);
var signature = auth.signQuery({
amazonHeaders: 'response-content-disposition:attachment',
secret: this.secret,
date: epoch,
resource: '/' + this.bucket + url.parse(filename).pathname
});
return this.url(filename) +
'?Expires=' + epoch +
'&AWSAccessKeyId=' + this.key +
'&Signature=' + encodeURIComponent(signature);
};
Shai.
maybe one too many newlines?
var stringToSign ='GET\n\n\n' + date + '\n\n' + resource;
If its any help here is a rubbish PHP implementation which definitely works:
class myS3Helper{
public function getSignedImageLink($timeout = 1800)
{
$now = new Zend_Date(); //Gives us a time object that is set to NOW
$now->setTimezone('UTC'); //Set to UTC a-la AWS requirements
$now->addSecond($timeout);
$expirationTime = $now->getTimestamp(); //returns unix timestamp representation of the time.
$signature = urlencode(
base64_encode(
hash_hmac(
'sha1', $this->_generateStringToSign($expirationTime),
$my_aws_secretkey,
true
)
)
);
//FIXME make this less ugly when I know it works
$url = 'https://';
$url .= Zend_Service_Amazon_S3::S3_ENDPOINT; //e.g s3.amazonaws.com
$url .= $this->_getImagePath(); //e.g /mybucket/myFirstCar.jpg
$url .='?AWSAccessKeyId=' . $my_aws_key;
$url .='&Signature=' . $signature; //signature as returned by below function
$url .='&Expires=' . $expirationTime;
return $url;
}
protected function _generateStringToSign($expires)
{
$string = "GET\n"; //Methods
$string .= "\n";
$string .= "\n";
$string .= "$expires\n"; //Expires
$string .= $this->_getImagePath();
return $string;
}
}
EDIT--
Have a look at this node.js s3 upload code, (it's not mine but found it lying around on my mac - so if anyone can attribute it to someone let me know and i'll do the props). Hopefully this might help (3rd time lucky)
https://gist.github.com/1370593
My implementation using AWS-SDK and Rx.
import AWS from "aws-sdk"
import Rx from 'rx'
/*
* Credentials could be loaded from env variables
* http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html
* */
const s3 = new AWS.S3({apiVersion: '2006-03-01'});
export function getS3SignedImage(objectKey) {
return Rx.Observable.create(function (observer) {
s3.getSignedUrl('getObject',{
Bucket: process.env.AWS_BUCKET,
Key: objectKey
}, (err, data) => {
if (err) {
return observer.onError(err);
}
observer.onNext(data);
observer.onCompleted();
});
});
}

Categories

Resources