C# 使用nodejs生成ASP.NET网页\u成员密码
现有的系统正在C#上工作,但我们已经决定将C#网站的一些模块移动到nodejs,所以我也可以通过C#和nodejs登录。使用c#注册,它使用asp.net的一些现有库生成了一些随机密码,它将密码存储到的“webpages\u membership”表中 C#生成的随机密码:“JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C”是“123456”字符串的散列密码 所以现在有一些其他模块现在将在nodejs中,但其余部分将只在c#中。所以现在登录我必须通过节点登录 我试图使用以下方法比较nodejs中c#生成的密码 图书馆 但它返回False c#为“123456”生成的密码=>“JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C” 请帮助我在nodejs中实现同样的目标 nodejscodeC# 使用nodejs生成ASP.NET网页\u成员密码,c#,php,node.js,C#,Php,Node.js,现有的系统正在C#上工作,但我们已经决定将C#网站的一些模块移动到nodejs,所以我也可以通过C#和nodejs登录。使用c#注册,它使用asp.net的一些现有库生成了一些随机密码,它将密码存储到的“webpages\u membership”表中 C#生成的随机密码:“JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C”是“123456”字符串的散列密码 所以现在有一些其他模块现在将在nodejs中,但其余部分将
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
我用下面的代码在php中做了同样的尝试,效果很好。使用下面的php代码,我可以比较c生成的密码,也可以从php生成新密码,还可以从c登录
工作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);
根据文档,您应该将密码作为validatePassword中的第一个参数发送。所以试试这个:
var passwordHasher=require('aspnet-identity-pw');
var hashedPassword=passwordHasher.hashPassword('123456');
console.log(hashedPassword);
var isValid=passwordHasher.validatePassword('123456',hashedPassword);
console.log(“结果:+isValid”)代码>您可以使用内置模块将工作PHP代码移植到Node.js
创建哈希:
<?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);
在PHPMembershipModel::encript
方法中,您正在使用PBKDF2实现来创建密钥。我们可以使用在Node.js中创建相同的密钥
现在我们可以编写一个函数,它使用kdf
并返回salt和key,base64编码,格式与PHP和C代码相同
对于我使用的salt,它是一个CSPRNG函数(创建安全的伪随机数据)
检查哈希:
<?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);
在您的PHPMembershipModel::verifypassword
方法中,您正在使用收到的salt将一个密钥与PBKDF2关联,然后将新密钥与收到的密钥进行比较。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);
}
我用它来比较钥匙;它执行恒定时间比较
测试:
<?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);
我们可以看到,hashPassword
函数生成的哈希值与PHP代码兼容,verifyPassword
可以成功验证它们
关于PHP代码的一些注意事项:
我假设MembershipModel::bytearraysequal
方法应该使用一个常数时间算法,但它在第一次出现不相等字符时返回false。更好的实现,使用位运算符:
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);
}
您的密钥派生方案似乎足够安全:带有随机salt和长密钥的PBKDF2。您可以增加迭代次数以获得更好的安全性,但这将花费时间和性能
但是,该实现可能存在可能降低代码安全性的bug(如我在MembershipModel::bytearraysequal
中发现的bug)。如果可能的话,最好使用内置函数
更新
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
在研究了aspnet identity pw
的源代码后,我发现它在内部使用了加密。密钥由crypto.pbkdf2
创建,具有16字节的salt和1000次迭代。唯一的区别是它创建了一个49字节的散列,前面是一个零字节
散列格式是0+salt[16]+key[32]
,因此如果我们切掉第一个字节,就可以使用这个散列。例如:
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
此代码还生成与PHP代码兼容的结果。就个人而言,我宁愿直接使用crypto
,因为它更灵活,在比较散列时也可以使用aspnet identity pw
。但我知道,aspnet identity pw
可能更容易使用,对经验不足的用户来说可能更安全。hashedPassword将是使用网页成员模块生成的c#密码,该模块生成一些随机密码,如“JWvppSSfnzOQ+uMd+BORpT/8aQorC8y05Bjbo/8w/9b/eiG4WLzUFRQSSiKZqo3C”