1

I'm trying to dynamically generate a security header at Postman pre-request script. To do so, I need to transform the following code snippet from PHP to JS.

$password = "SECRETPASSWORD";
$nonce = random_bytes(32);
date_default_timezone_set("UTC");
$created = date(DATE_ATOM);
$encodedNonce = base64_encode($nonce);
$passwordHash = base64_encode(sha1($nonce . $created . sha1($password, true), true));

(Note the true flag at php's sha1() function, forcing raw output).

I've coded this code snippet so far:

var uuid = require('uuid');
var CryptoJS = require('crypto-js');
var moment = require('moment');

// Generate messageId
var messageId = uuid.v4();
pm.environment.set('messageId', messageId);


// Generate nonce
var nonce = uuid.v4();
var encodedNonce = CryptoJS.enc.Base64.stringify(
    CryptoJS.enc.Utf8.parse(nonce)
);
pm.environment.set('nonce', encodedNonce);

// Generate created
var created = moment().utc().format();
pm.environment.set('created', created);

// Generate password hash
var password = 'SECRETPASSWORD';
var rawSha1Password = Buffer.from(CryptoJS.SHA1(password).toString(CryptoJS.enc.Base64), "base64").toString("utf8");
var passwordHash =  CryptoJS.SHA1(nonce + created + rawSha1Password).toString(CryptoJS.enc.Base64);
pm.environment.set('passwordHash', passwordHash);

My JS script is almost working, the only problem seems to be the sha1 generation. Taking the following example values:

password: SECRETPASSWORD
nonce: 55d61876-f882-42f0-b390-dc662a7e7279
created: 2021-01-21T18:19:32Z

The output from PHP is:

encodedNonce: NTVkNjE4NzYtZjg4Mi00MmYwLWIzOTAtZGM2NjJhN2U3Mjc5
passwordHash: olI18mUowhmeCwjb1FJNHtTHYDA=

But, the output from JS is:

encodedNonce: NTVkNjE4NzYtZjg4Mi00MmYwLWIzOTAtZGM2NjJhN2U3Mjc5
passwordHash: tk/uYkL/3Uq0oIkYO0nlBGnV/0E=

As you can see, the encodedNonce is built correctly; however the passwordHash value is different. As I'm using Postman, I have a limited JS libraries available. Taking this into account, how can I get the same result as the PHP one?

2
  • rawSha1Password seems base 64 encoded when you hash it. It needs to be raw binary (this is why true is in the call) when you rehash it. I.e. just the output of CryptoJS.SHA1(password) if I'm not mistaken. Commented Jan 21, 2021 at 18:57
  • Thanks for your contribution. The CryptoJS.SHA1(password) returns an WordsArray object; that's why I'm encoding to Base64 before creating the Buffer. If I try to encode to Utf8 at this point, the library throws an Error: Malformed Utf8 string. Commented Jan 21, 2021 at 19:43

1 Answer 1

4

In the line

var rawSha1Password = Buffer.from(CryptoJS.SHA1(password).toString(CryptoJS.enc.Base64), "base64").toString("utf8");

the password hash is read into a buffer and then UTF-8 decoded. The UTF-8 decoding generally corrupts the data, see here. A possible solution is to concatenate the WordArrays instead of the strings:

function getPasswordHash(test){

    // Generate nonce
    var nonceWA = !test ? CryptoJS.lib.WordArray.random(32) : CryptoJS.enc.Utf8.parse('55d61876-f882-42f0-b390-dc662a7e7279'); 
    console.log('nonce (Base64):  ' + nonceWA.toString(CryptoJS.enc.Base64)); 

    // Generate created
    var created = !test ? moment().utc().format('YYYY-MM-DDTHH:mm:ss[Z]') : '2021-01-21T18:19:32Z'; 
    var createdWA = CryptoJS.enc.Utf8.parse(created);
    console.log('created:         ' + created); 

    // Hash password
    var pwd = 'SECRETPASSWORD';
    var pwdHashWA =  CryptoJS.SHA1(pwd);

    // Hash nonce + created + pwd
    var passwordHash =  CryptoJS.SHA1(nonceWA.concat(createdWA).concat(pwdHashWA)).toString(CryptoJS.enc.Base64);

    console.log('passwordHash:    ' + passwordHash);
}

getPasswordHash(true);    // with testdata
getPasswordHash(false);   // without testdata
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

When the code is executed, the first call of getPasswordHash() uses the test data for nonce and date (test=true), the second call applies a random nonce and the current date (test=false) . The call with the test data returns the same result as the PHP code:

nonce (Base64):  NTVkNjE4NzYtZjg4Mi00MmYwLWIzOTAtZGM2NjJhN2U3Mjc5
created:         2021-01-21T18:19:32Z
passwordHash:    olI18mUowhmeCwjb1FJNHtTHYDA=

CryptoJS implements CryptoJS.lib.WordArray.random() as CSPRNG, which is the counterpart of the PHP method random_bytes(). The use of the uuid library is therefore actually not necessary. I have chosen CryptoJS.lib.WordArray.random() in my example because it is closest to the PHP code.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.