JavaScript: How to generate Rfc2898DeriveBytes like C #?

EDIT:. For discussion in the comments, let me clarify that this will happen on the server side, for SSL. I do not intend to provide the client with a hashed password or hash scheme.

Suppose we have an existing asp.net identifier database with standard tables (aspnet_Users, aspnet_Roles, etc.). Based on my understanding, the password hashing algorithm uses sha256 and saves salt + (hashed password) as a base64 encoded string. EDIT: This assumption is incorrect, see answer below.

I would like to replicate the class function Microsoft.AspNet.Identity.Crypto ' VerifyHashedPassword with a version of JavaScript.

Let's say that the password is welcome1 , and its hash password is asp.net ADOEtXqGCnWCuuc5UOAVIvMVJWjANOA / LoVy0E4XCyUHIfJ7dfSY0Id + uJ20DTtG + A ==

So far, I have been able to reproduce parts of the method that get the salt and stored auxiliary key.

If the C # implementation does more or less:

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 JavaScript in JavaScript (not elegant by anything):

 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, this is ugly, but after starting this fragment, the solibytes and storedSubKeyBytes coincide with the byte for the byte, which I see in the C # debugger for salt and storedSubkey.

Finally, in C #, an instance of Rfc2898DeriveBytes is used to generate a new subsection based on the salt and password provided, for example:

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

This is where I am stuck. I tried other solutions, such as this one , I used Google and Node CryptoJS and crypto libraries respectively, and my output never generates anything similar to 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 found on the Internet point to problems related to coding inconsistencies (NodeJS / UTF-8 compared to .NET / UTF-16LE), so I tried coding using the standard .NET coding format, but to no avail.

Or I could be completely wrong in what I assume these libraries do. But any pointers in the right direction would be greatly appreciated.

+8
javascript c # asp.net-identity cryptojs rfc2898
source share
4 answers

Well, I think this problem turned out to be a little easier than I did (not always). After performing the RTFM operation in the pbkdf2 spec , I spent several side by side using cryptographic cryptographic and .NET cryptographic curves. I made quite good progress in the solution.

The following JavaScript code correctly parses the saved salt and subkey, and then validates the password set by hashing it with the saved salt. There are undoubtedly better / cleaner / safer tweaks, so comments are welcome.

 // NodeJS implementation of crypto, I'm sure google // 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!"); } 
+12
source share

The previous solution will not work in all cases. Let them say that you want to compare the source password against the hash in the hash database, which may be technically possible if the database is at risk, the function will return true , because the subkey is an empty string.

Modify the function to catch this and return false instead.

 // NodeJS implementation of crypto, I'm sure google // 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]; } } if (storedSubKeyString === '') { return false } // 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!"); } 
+1
source share

Here is another option that actually compares bytes rather than converting them 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 😎' : 'incorrect 😭')); } ); 

1000 is the default number of iterations in System.Security.Cryptography.Rfc2898DeriveBytes, and 20 is the number of bytes that we use to store salt (again by default).

+1
source share

I know this is pretty late, but I ran into a problem playing C # Rfc2898DeriveBytes.GetBytes in Node and kept returning to this SO answer. I ended up creating a minimal class for my own use, and decided that I would share it if someone had the same problems. This is not perfect, but it works.

 const crypto = require('crypto'); const $key = Symbol('key'); const $saltSize = Symbol('saltSize'); const $salt = Symbol('salt'); const $iterationCount = Symbol('iterationCount'); const $position = Symbol('position'); class Rfc2898DeriveBytes { constructor(key, saltSize = 32, iterationCount = 1000) { this[$key] = key; this[$saltSize] = saltSize; this[$iterationCount] = iterationCount; this[$position] = 0; this[$salt] = crypto.randomBytes(this[$saltSize]); } get salt() { return this[$salt]; } set salt(buffer) { this[$salt] = buffer; } get iterationCount() { return this[$iterationCount]; } set iterationCount(count) { this[$iterationCount] = count; } getBytes(byteCount) { let position = this[$position]; let bytes = crypto.pbkdf2Sync(Buffer.from(this[$key]), this.salt, this.iterationCount, position + byteCount, 'sha1'); this[$position] += byteCount; let result = Buffer.alloc(byteCount); for (let i = 0; i < byteCount; i++) { result[i] = bytes[position + i]; } return result; } } module.exports = Rfc2898DeriveBytes; 
0
source share

All Articles