admin管理员组文章数量:1201026
EDIT: Per discussion in the comments, let me clarify that this will be happening server side, behind SSL. I do not intend to expose the hashed password or the hashing scheme to the client.
Assume we have an existing asp identity database with the default tables (aspnet_Users, aspnet_Roles, etc.). Based on my understanding, the password hashing algorithm uses sha256 and stores the salt + (hashed password) as a base64 encoded string. EDIT: This assumption is incorrect, see answer below.
I would like to replicate the function of the Microsoft.AspNet.Identity.Crypto class' VerifyHashedPassword function with a JavaScript version.
Let's say that a password is welcome1 and its asp hashed password is ADOEtXqGCnWCuuc5UOAVIvMVJWjANOA/LoVy0E4XCyUHIfJ7dfSY0Id+uJ20DTtG+A==
So far I have been able to reproduce the parts of the method that get the salt and the stored sub key.
Where the C# implementation does more or less this:
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);
I have the following in JavaScript (not elegant by any stretch):
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]);
}
}
Again, it ain't pretty, but after running this snippet the saltbytes and storedSubKeyBytes match byte for byte what I see in the C# debugger for salt and storedSubkey.
Finally, in C#, an instance of Rfc2898DeriveBytes is used to generate a new subkey based on the salt and the password provided, like so:
byte[] generatedSubkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, PBKDF2IterCount))
{
generatedSubkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
This is where I'm stuck. I have tried others' solutions such as this one, I have used Google's and Node's CryptoJS and crypto libraries respectively, and my output never generates anything resembling the C# version.
(Example:
var output = crypto.pbkdf2Sync(new Buffer('welcome1', 'utf16le'),
new Buffer(parsedSaltString), 1000, 32, 'sha256');
console.log(output.toString('base64'))
generates "LSJvaDM9u7pXRfIS7QDFnmBPvsaN2z7FMXURGHIuqdY=")
Many of the pointers I've found online indicate problems involving encoding mismatches (NodeJS / UTF-8 vs. .NET / UTF-16LE), so I've tried encoding using the default .NET encoding format but to no avail.
Or I could be completely wrong about what I assume these libraries are doing. But any pointers in the right direction would be much appreciated.
EDIT: Per discussion in the comments, let me clarify that this will be happening server side, behind SSL. I do not intend to expose the hashed password or the hashing scheme to the client.
Assume we have an existing asp.net identity database with the default tables (aspnet_Users, aspnet_Roles, etc.). Based on my understanding, the password hashing algorithm uses sha256 and stores the salt + (hashed password) as a base64 encoded string. EDIT: This assumption is incorrect, see answer below.
I would like to replicate the function of the Microsoft.AspNet.Identity.Crypto class' VerifyHashedPassword function with a JavaScript version.
Let's say that a password is welcome1 and its asp.net hashed password is ADOEtXqGCnWCuuc5UOAVIvMVJWjANOA/LoVy0E4XCyUHIfJ7dfSY0Id+uJ20DTtG+A==
So far I have been able to reproduce the parts of the method that get the salt and the stored sub key.
Where the C# implementation does more or less this:
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);
I have the following in JavaScript (not elegant by any stretch):
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]);
}
}
Again, it ain't pretty, but after running this snippet the saltbytes and storedSubKeyBytes match byte for byte what I see in the C# debugger for salt and storedSubkey.
Finally, in C#, an instance of Rfc2898DeriveBytes is used to generate a new subkey based on the salt and the password provided, like so:
byte[] generatedSubkey;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, PBKDF2IterCount))
{
generatedSubkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
This is where I'm stuck. I have tried others' solutions such as this one, I have used Google's and Node's CryptoJS and crypto libraries respectively, and my output never generates anything resembling the C# version.
(Example:
var output = crypto.pbkdf2Sync(new Buffer('welcome1', 'utf16le'),
new Buffer(parsedSaltString), 1000, 32, 'sha256');
console.log(output.toString('base64'))
generates "LSJvaDM9u7pXRfIS7QDFnmBPvsaN2z7FMXURGHIuqdY=")
Many of the pointers I've found online indicate problems involving encoding mismatches (NodeJS / UTF-8 vs. .NET / UTF-16LE), so I've tried encoding using the default .NET encoding format but to no avail.
Or I could be completely wrong about what I assume these libraries are doing. But any pointers in the right direction would be much appreciated.
Share Improve this question edited May 23, 2017 at 12:03 CommunityBot 11 silver badge asked Feb 24, 2015 at 21:18 GojiraGojira 3,0414 gold badges22 silver badges32 bronze badges 5- Are you trying to generate password hash on the client and pass the hash down to the server for validation? – trailmax Commented Feb 25, 2015 at 0:53
- No, I'm trying to generate the hash server side in node.js. Essentially, keeping the database the same but swapping out the IIS / asp.net layer for node. I'm not a security expert but I would be wary of trying to do any password operations client side. – Gojira Commented Feb 25, 2015 at 16:28
- Ah, that clarifies my concerns. I'd mention this in your question. Sorry, can't actually help with JS-side of things here( – trailmax Commented Feb 25, 2015 at 17:01
- @trailmax Could you please explain your concerns with client-side hashing a little further? – bonh Commented Jun 17, 2015 at 19:12
- 1 @bonh See this explanation security.stackexchange.com/a/53606 – trailmax Commented Jun 17, 2015 at 19:14
4 Answers
Reset to default 16Ok, I think this problem ended up being quite a bit simpler than I was making it (aren't they always). After performing a RTFM operation on the pbkdf2 spec, I ran some side-by-side tests with Node crypto and .NET crypto, and have made pretty good progress on a solution.
The following JavaScript code correctly parses the stored salt and subkey, then verifies the given password by hashing it with the stored salt. There are doubtless better / cleaner / more secure tweaks, so comments welcome.
// 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!");
}
Here's another option which actually compares the bytes as opposed to converting to a string representation.
const crypto = require('crypto');
const password = 'Password123';
const storedHashString = 'J9IBFSw0U1EFsH/ysL+wak6wb8s=';
const storedSaltString = '2nX0MZPZlwiW8bYLlVrfjBYLBKM=';
const storedHashBytes = new Buffer.from(storedHashString, 'base64');
const storedSaltBytes = new Buffer.from(storedSaltString, 'base64');
crypto.pbkdf2(password, storedSaltBytes, 1000, 20, 'sha1',
(err, calculatedHashBytes) => {
const correct = calculatedHashBytes.equals(storedHashBytes);
console.log('Password is ' + (correct ? 'correct
本文标签:
JavaScript How to generate Rfc2898DeriveBytes like CStack Overflow
版权声明:本文标题:JavaScript: How to generate Rfc2898DeriveBytes like C#? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人,
转载请联系作者并注明出处:http://www.betaflare.com/web/1738559350a2098926.html,
本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论