如何像 PHP 那样使用 JS 从 SHA1 获取原始输出?

How to get raw output from SHA1 using JS as PHP does?

我正在尝试在 Postman pre-request 脚本 上动态生成安全性 header。为此,我需要将以下代码片段从 PHP 转换为 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));

(注意 php's sha1() function 处的 true 标志,强制原始输出)。

到目前为止我已经编写了这段代码:

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);

我的 JS 脚本几乎可以正常工作,唯一的问题似乎是 sha1 生成。采用以下示例值:

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

PHP 的输出是:

encodedNonce: NTVkNjE4NzYtZjg4Mi00MmYwLWIzOTAtZGM2NjJhN2U3Mjc5
passwordHash: olI18mUowhmeCwjb1FJNHtTHYDA=

但是,JS 的输出是:

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

如您所见,已正确构建 encodedNonce;但是 passwordHash 值不同。当我使用 Postman 时,我可用的 JS 库有限。 考虑到这一点,我怎样才能得到与 PHP 相同的结果?

在行

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

密码哈希被读入缓冲区,然后进行 UTF-8 解码。 UTF-8 解码通常会损坏数据,请参阅 here。一种可能的解决方案是连接 WordArray 而不是字符串:

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>

执行代码时,getPasswordHash() 的第一次调用使用随机数和日期 (test=true) 的测试数据,第二次调用应用随机数和当前日期 (test=false) 。用测试数据调用 returns 结果与 PHP 代码相同:

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

CryptoJS 实现 CryptoJS.lib.WordArray.random() as CSPRNG, which is the counterpart of the PHP method random_bytes()。因此实际上没有必要使用 uuid 库。我在示例中选择了 CryptoJS.lib.WordArray.random(),因为它最接近 PHP 代码。