JavaScript:如何生成像C#这样的Rfc2898DeriveBytes?

Goj*_*tah 8 javascript c# rfc2898 cryptojs asp.net-identity

编辑:在评论中的每次讨论中,让我澄清一下,这将发生在SSL后面的服务器端.我不打算将散列密码或散列方案暴露给客户端.

假设我们有一个现有的asp.net身份数据库,其中包含默认表(aspnet_Users,aspnet_Roles等).根据我的理解,密码哈希算法使用sha256并将salt +(哈希密码)存储为base64编码的字符串.编辑:这个假设不正确,请参阅下面的答案.

我想用JavaScript版本复制Microsoft.AspNet.Identity.Crypto类' VerifyHashedPassword函数的功能.

假设密码是welcome1,其asp.net散列密码为 ADOEtXqGCnWCuuc5UOAVIvMVJWjANOA/LoVy0E4XCyUHIfJ7dfSY0Id + uJ20DTtG + A ==

到目前为止,我已经能够重现获取salt和存储的子键的方法部分.

C#实现或多或少地执行此操作:

var salt = new byte[SaltSize];
Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, SaltSize);
var storedSubkey = new byte[PBKDF2SubkeyLength];
Buffer.BlockCopy(hashedPasswordBytes, 1 + SaltSize, storedSubkey, 0, PBKDF2SubkeyLength);
Run Code Online (Sandbox Code Playgroud)

我在JavaScript中有以下内容(任何方面都不优雅):

var hashedPwd = "ADOEtXqGCnWCuuc5UOAVIvMVJWjANOA/LoVy0E4XCyUHIfJ7dfSY0Id+uJ20DTtG+A==";
var hashedPasswordBytes = new Buffer(hashedPwd, 'base64');
var saltbytes = [];
var storedSubKeyBytes = [];

for(var i=1;i<hashedPasswordBytes.length;i++)
{
  if(i > 0 && i <= 16)
  {
    saltbytes.push(hashedPasswordBytes[i]);
  }
  if(i > 0 && i >16) {
    storedSubKeyBytes.push(hashedPasswordBytes[i]);
  }
}
Run Code Online (Sandbox Code Playgroud)

同样,它并不漂亮,但在运行此片段之后,saltbytes和storedSubKeyBytes匹配字节,这是我在C#调试器中看到的salt和storedSubkey.

最后,在C#中,Rfc2898DeriveBytes的一个实例用于根据提供的salt和密码生成一个新的子键,如下所示:

byte[] generatedSubkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, PBKDF2IterCount))
{
   generatedSubkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
Run Code Online (Sandbox Code Playgroud)

这就是我被困住的地方.我已经尝试过其他人的解决方案,比如这个,我分别使用了Google和Node的CryptoJS和加密库,我的输出永远不会生成类似C#版本的东西.

(例:

var output = crypto.pbkdf2Sync(new Buffer('welcome1', 'utf16le'), 
    new Buffer(parsedSaltString), 1000, 32, 'sha256');
console.log(output.toString('base64'))
Run Code Online (Sandbox Code Playgroud)

生成"LSJvaDM9u7pXRfIS7QDFnmBPvsaN2z7FMXURGHIuqdY =")

我在网上找到的许多指针都表明涉及编码不匹配的问题(NodeJS/UTF-8与.NET/UTF-16LE),所以我尝试使用默认的.NET编码格式进行编码,但无济于事.

或者我可能完全错误地认为这些库正在做什么.但任何指向正确方向的人都会非常感激.

Goj*_*tah 14

好吧,我认为这个问题最终比我做的要简单得多(不是他们总是).在pbkdf2规范上执行RTFM操作后,我使用Node crypto和.NET crypto进行了一些并行测试,并在解决方案上取得了相当不错的进展.

以下JavaScript代码正确解析存储的salt和subkey,然后通过使用存储的salt对其进行散列来验证给定的密码.毫无疑问,更好/更清洁/更安全的调整,所以评论欢迎.

// NodeJS implementation of crypto, I'm sure google's 
// cryptoJS would work equally well.
var crypto = require('crypto');

// The value stored in [dbo].[AspNetUsers].[PasswordHash]
var hashedPwd = "ADOEtXqGCnWCuuc5UOAVIvMVJWjANOA/LoVy0E4XCyUHIfJ7dfSY0Id+uJ20DTtG+A==";
var hashedPasswordBytes = new Buffer(hashedPwd, 'base64');

var hexChar = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];

var saltString = "";
var storedSubKeyString = "";

// build strings of octets for the salt and the stored key
for (var i = 1; i < hashedPasswordBytes.length; i++) {
    if (i > 0 && i <= 16) {
        saltString += hexChar[(hashedPasswordBytes[i] >> 4) & 0x0f] + hexChar[hashedPasswordBytes[i] & 0x0f]
    }
    if (i > 0 && i > 16) {
        storedSubKeyString += hexChar[(hashedPasswordBytes[i] >> 4) & 0x0f] + hexChar[hashedPasswordBytes[i] & 0x0f];
    }
}

// password provided by the user
var password = 'welcome1';

// TODO remove debug - logging passwords in prod is considered 
// tasteless for some odd reason
console.log('cleartext: ' + password);
console.log('saltString: ' + saltString);
console.log('storedSubKeyString: ' + storedSubKeyString);

// This is where the magic happens. 
// If you are doing your own hashing, you can (and maybe should)
// perform more iterations of applying the salt and perhaps
// use a stronger hash than sha1, but if you want it to work
// with the [as of 2015] Microsoft Identity framework, keep
// these settings.
var nodeCrypto = crypto.pbkdf2Sync(new Buffer(password), new Buffer(saltString, 'hex'), 1000, 256, 'sha1');

// get a hex string of the derived bytes
var derivedKeyOctets = nodeCrypto.toString('hex').toUpperCase();

console.log("hex of derived key octets: " + derivedKeyOctets);

// The first 64 bytes of the derived key should
// match the stored sub key
if (derivedKeyOctets.indexOf(storedSubKeyString) === 0) {
    console.info("passwords match!");
} else {
    console.warn("passwords DO NOT match!");
}
Run Code Online (Sandbox Code Playgroud)

  • 你先生刚刚救了我的命.非常感谢.我正在从ASP.NET迁移到node.js,现在我不必告诉我的用户他们的密码已经过期了!:d (4认同)
  • 这也为我的ASP.Net节点项目转换节省了一天!谢谢! (2认同)
  • @GojiraDeMonstah谢谢!在需要使用现有.Net数据库进行身份验证的节点应用程序上工作,这正是我所需要的.如果可以,我会投票两次;) (2认同)