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?

Isaac Bosca
  • 1,588
  • 1
  • 15
  • 34
  • `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. – Maarten Bodewes Jan 21 '21 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. – Isaac Bosca Jan 21 '21 at 19:43

1 Answers1

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.

Topaco
  • 40,594
  • 4
  • 35
  • 62