[英]Generate ASP.NET webpages_membership Password using nodejs
There is existing system is working on C#, but we have decided to move some modules of c# website into nodejs, so i will be able to login through c# and nodejs too. 现有系统正在研究C#,但我们决定将c#网站的一些模块移动到nodejs中,所以我也可以通过c#和nodejs登录。 using c# registration it generated some random password using some existing library of asp.net it will store password into "webpages_membership" table. 使用c#注册它使用一些现有的asp.net库生成一些随机密码,它会将密码存储到“webpages_membership”表中。
C# generated random password : "JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C" is hashed password for "123456" string. C#生成的随机密码: “JWvppSSfnzOQ + uMd + BORpT / 8aQorC8y05Bjbo / 8w / 9b / eiG4WLzUFRQSSiKZqo3C”是“123456”字符串的哈希密码。
so now there is some other module which will be now in nodejs but rest of the things will be in c# only. 所以现在有一些其他模块现在将在nodejs中,但其余部分将只在c#中。 so login now i have to login through node. 所以现在登录我必须通过节点登录。
I am trying to compare c# generated password in nodejs using following library https://www.npmjs.com/package/aspnet-identity-pw 我试图使用以下库比较nodejs中的c#生成的密码https://www.npmjs.com/package/aspnet-identity-pw
but it returns False. 但它返回False。
c# generated password for "123456" => "JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C" c#生成的密码为“123456”=>“JWvppSSfnzOQ + uMd + BORpT / 8aQorC8y05Bjbo / 8w / 9b / eiG4WLzUFRQSSiKZqo3C”
Please help me to achieve same in nodejs. 请帮我在nodejs中实现相同功能。
nodejscode nodejscode
var passwordHasher = require('aspnet-identity-pw');
var hashedPassword = passwordHasher.hashPassword('123456');
console.log(hashedPassword);
var isValid = passwordHasher.validatePassword('JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C', hashedPassword);
console.log("Result:"+isValid);
//Return False
I tried same thing in php using below code which is working fine, using below php code i am able to compare c# generated password and also able to generate new password from php and able to login from C#. 我在php中使用下面的代码尝试了同样的事情,这是正常工作,使用下面的PHP代码,我能够比较c#生成的密码,并能够从PHP生成新密码,并能够从C#登录。
Working PHPcode for reference: 使用PHPcode作为参考:
<?php
/*
* Author : Mr. Juned Ansari
* Date : 15/02/2017
* Purpose : It Handles Login Encryption And Decryption Related Activities
*/
class MembershipModel {
function bytearraysequal($source, $target) {
if ($source == null || $target == null || (strlen($source) != strlen($target)))
return false;
for ($ctr = 0; $ctr < strlen($target); $ctr++) {
if ($target[$ctr] != $source[$ctr])
return false;
}
return true;
}
//This Function is Used to verifypassword
function verifypassword($hashedPassword, $password) {
$PBKDF2IterCount = 1000; // default for Rfc2898DeriveBytes
$PBKDF2SubkeyLength = 32; // 256 bits
$SaltSize = 16; // 128 bits
if ($hashedPassword == null) {
return false;
//show_error("hashedPassword is null");
}
if ($password == null) {
return false;
//show_error("Password is null");
}
$hashedPasswordBytes = base64_decode($hashedPassword);
if (strlen($hashedPasswordBytes) != 48) {
return false;
}
$salt = substr($hashedPasswordBytes, 0, $SaltSize);
$storedSubkey = substr($hashedPasswordBytes, $SaltSize, $PBKDF2SubkeyLength);
$generatedSubkey = $this->encript('sha1', $password, $salt, $PBKDF2IterCount, $PBKDF2SubkeyLength, true);
return $this->bytearraysequal($storedSubkey, $generatedSubkey);
}
function encript($algorithm, $password, $salt, $count, $key_length, $raw_output = false) {
$algorithm = strtolower($algorithm);
if (!in_array($algorithm, hash_algos(), true))
return false;
//show_error('PBKDF2 ERROR: Invalid hash algorithm.');
if ($count <= 0 || $key_length <= 0)
return false;
//show_error('PBKDF2 ERROR: Invalid parameters.');
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for ($i = 1; $i <= $block_count; $i++) {
$last = $salt . pack("N", $i);
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
for ($j = 1; $j < $count; $j++) {
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
}
$output .= $xorsum;
}
return substr($output, 0, $key_length);
}
}
---------------------------------
echo MembershipModel::verifypassword("JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C","123456");
//Returns True for every c# generated password
$salt = openssl_random_pseudo_bytes(16);
$dev = MembershipModel::encript('sha1', $Password, $salt, 1000, 32, true);
$HashedPassword = base64_encode($salt.$dev);
You can port your working PHP code to Node.js using the built-in crypto
module. 您可以使用内置的crypto
模块将工作的PHP代码移植到Node.js。
Creating a hash: 创建哈希:
In your PHP MembershipModel::encript
method you're using a PBKDF2 implementation to create a key. 在PHP MembershipModel::encript
方法中,您使用PBKDF2实现来创建密钥。 We can create the same key in Node.js with crypto.pbkdf2Sync
. 我们可以使用crypto.pbkdf2Sync
在Node.js中创建相同的密钥。
function kdf(password, salt, count=1000, keyLen=32, hash='sha1') {
return crypto.pbkdf2Sync(password, salt, count, keyLen, hash);
}
Now we can write a function that uses kdf
and returns the salt and key, base64 encoded - the same format as your PHP and C# code. 现在我们可以编写一个使用kdf
的函数并返回salt和key,base64编码 - 与PHP和C#代码的格式相同。
function hashPassword(password) {
var salt = crypto.randomBytes(16);
var key = kdf(password, salt, 1000, 32, 'sha1');
var sk = Buffer.concat([salt, key]);
return sk.toString('base64');
}
For the salt I've used crypto.randomBytes
which is a CSPRNG function (creates secure pseudo-random data). 对于salt,我使用了crypto.randomBytes
这是一个CSPRNG函数(创建安全的伪随机数据)。
Checking a hash: 检查哈希:
In your PHP MembershipModel::verifypassword
method you're using the received salt to craete a key with PBKDF2, then compare the new key with the received key. 在您的PHP MembershipModel::verifypassword
方法中,您使用收到的盐来使用PBKDF2来获取密钥,然后将新密钥与收到的密钥进行比较。 A Node.js equivalent: 一个Node.js等价物:
function verifyPassword(hashedPassword, password) {
var data = new Buffer(hashedPassword, 'base64');
var salt = data.slice(0, 16);
var key = data.slice(16);
var hash = kdf(password, salt);
return crypto.timingSafeEqual(key, hash);
}
I'm using crypto.timingSafeEqual
to compare the keys; 我正在使用crypto.timingSafeEqual
来比较密钥; it performs constant time comparison. 它执行恒定的时间比较。
Testing: 测试:
var password = "123456";
var hash = hashPassword(password);
console.log(hash);
//umzeh4aAeD1Ee6z4oN/BS9f2s2GQ7gswtbrguEr2C32c8XK99UjI8LkgYapbX8/N
console.log(verifyPassword(hash, password));
//true
hash = "JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C";
console.log(verifyPassword(hash, password));
//true
We can see that the hashPassword
function produces hashes that are compatible with your PHP code and verifyPassword
can verify them successfully. 我们可以看到hashPassword
函数生成与PHP代码兼容的哈希值, verifyPassword
可以成功验证它们。
Some notes about your PHP code: 关于PHP代码的一些注意事项:
I assume that the MembershipModel::bytearraysequal
method is supposed to use a constant time algorithm, but it returns false with the first occurrence of unequal characters. 我假设MembershipModel::bytearraysequal
方法应该使用常量时间算法,但它返回false,第一次出现不相等的字符。 A better implementation, using bitwise operators: 使用按位运算符的更好实现:
function bytearraysEqual(string $hash1, string $hash2): bool {
$result = 0;
for ($i=0; $i<strlen($hash1) && $i<strlen($hash2); $i++) {
$result |= ord($hash1[$i]) ^ ord($hash2[$i]);
}
return $result === 0 && strlen($hash1) === strlen($hash2);
}
This function checks all the characters, and the length of strings. 此函数检查所有字符和字符串的长度。 However, it's best to use the built-in hash_equals
function (requires PHP 5.6 or higher). 但是,最好使用内置的hash_equals
函数(需要PHP 5.6或更高版本)。 Similarly, you can use openssl_pbkdf2
to create the key (PHP 5.5 or higher). 同样,您可以使用openssl_pbkdf2
来创建密钥(PHP 5.5或更高版本)。
We can improve your MembershipModel
class using those functions, and type hinting (PHP 7) which doesn't require null checks, and produces cleaner code. 我们可以使用这些函数改进MembershipModel
类,并输入不需要空检查的提示(PHP 7),并生成更清晰的代码。
class MembershipModel {
const PBKDF2_ALGORITHM = "SHA1";
const PBKDF2_ITERATIONS = 1000;
const KEY_LENGTH = 32;
const SALT_LENGTH = 16;
function hashPassword(string $password): string {
$salt = openssl_random_pseudo_bytes(16);
$key = MembershipModel::kdf($password, $salt);
return base64_encode($salt.$key);
}
function verifyPassword(string $hashedPassword, string $password): bool {
$hashedPasswordBytes = base64_decode($hashedPassword);
$salt = substr($hashedPasswordBytes, 0, MembershipModel::SALT_LENGTH);
$key1 = substr($hashedPasswordBytes, MembershipModel::SALT_LENGTH);
$key2 = MembershipModel::kdf($password, $salt);
return hash_equals($key1, $key2);
}
private function kdf(string $password, string $salt): string {
$key = hash_pbkdf2(
MembershipModel::PBKDF2_ALGORITHM, $password, $salt,
MembershipModel::PBKDF2_ITERATIONS, MembershipModel::KEY_LENGTH,
true
);
return $key;
}
}
Your key derivation scheme seems secure enough: PBKDF2 with a random salt and long key. 您的密钥派生方案似乎足够安全:PBKDF2具有随机盐和长密钥。 You could increase the number of iterations for better security, but that would cost time and performance. 您可以增加迭代次数以获得更好的安全性,但这会花费时间和性能。
The implementation however may have bugs (like the one I found in MembershipModel::bytearraysequal
) that could reduce the security of your code. 但是,实现可能存在错误(如我在MembershipModel::bytearraysequal
找到的错误),这可能会降低代码的安全性。 It's best to use built-in functions, if that is possible. 如果可能的话,最好使用内置函数。
Update 更新
After studying the source code of aspnet-identity-pw
, I discovered that it uses crypto
internally. 在研究了aspnet-identity-pw
的源代码之后,我发现它在内部使用了crypto
。 The key is created by crypto.pbkdf2
with 16 byte salt and 1000 iterations. 密钥由crypto.pbkdf2
创建,具有16字节盐和1000次迭代。 The only difference is that it creates a 49 byte hash, with a zero byte at the front. 唯一的区别是它创建了一个49字节的散列,前面有一个零字节。
The hash format is 0 + salt[16] + key[32]
, so could use this hash if we slice off the first byte. 哈希格式为0 + salt[16] + key[32]
,因此如果切掉第一个字节,可以使用此哈希。 For example: 例如:
const passwordHasher = require('aspnet-identity-pw');
function hashPassword(password) {
var hash = passwordHasher.hashPassword(password);
var bytes = Buffer(hash, 'base64');
return bytes.slice(1).toString('base64');
}
function verifyPassword(hashedPassword, password) {
var bytes = new Buffer(hashedPassword, 'base64');
var hash = Buffer.concat([new Buffer([0x00]), bytes]).toString('base64');
return passwordHasher.validatePassword(password, hash);
}
var hash = "JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C";
console.log(verifyPassword(hash, '123456'))
//true
This code also produces results that are compatible with your PHP code. 此代码还生成与PHP代码兼容的结果。 Personally, I would rather use crypto
directly because it is more flexible, and also aspnet-identity-pw
doesn't use a constant time algorithm when comparing hashes. 就个人而言,我宁愿直接使用crypto
,因为它更灵活,并且aspnet-identity-pw
在比较哈希时也不使用恒定时间算法 。 But I understand that aspnet-identity-pw
may be easier to use, and it may be safer for less experienced users. 但我知道aspnet-identity-pw
可能更容易使用,对于经验不足的用户来说可能更安全。
According to the documentation you should send in the password as the first parameter in validatePassword. 根据文档,您应该将密码作为validatePassword中的第一个参数发送。 So try this: 试试这个:
var passwordHasher = require('aspnet-identity-pw'); var hashedPassword = passwordHasher.hashPassword('123456'); console.log(hashedPassword); var isValid = passwordHasher.validatePassword('123456', hashedPassword); console.log("Result:"+isValid);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.