2015-02-24 18 views
7

EDIT: Per Diskussion in den Kommentaren, lassen Sie mich klarstellen, dass dies Server-Seite passieren wird, hinter SSL. Ich beabsichtige nicht, das Hash-Passwort oder das Hashing-Schema dem Client zur Verfügung zu stellen.JavaScript: Wie Rfc2898DeriveBytes wie C# generieren?

Angenommen, wir haben eine vorhandene asp.net-Identitätsdatenbank mit den Standardtabellen (aspnet_Users, aspnet_Roles usw.). Basierend auf meinem Verständnis verwendet der Passwort-Hashing-Algorithmus sha256 und speichert das salt + (Hash-Passwort) als Base64-codierte Zeichenfolge. EDIT: Diese Annahme ist falsch, siehe Antwort unten.

Ich mag die Funktion der Microsoft.AspNet.Identity.Crypto Klasse replizieren VerifyHashedPassword Funktion mit einer JavaScript-Version.

Lassen Sie uns sagen, dass ein Passwort welcome1 und seine asp.net ist gehasht Passwort ADOEtXqGCnWCuuc5UOAVIvMVJWjANOA ist/LoVy0E4XCyUHIfJ7dfSY0Id + uJ20DTtG + A ==

Bisher habe ich die Teile des Verfahrens reproduzieren konnte, dass Holen Sie sich das Salz und den gespeicherten Unterschlüssel.

Wo die C# -Implementierung mehr oder weniger tut dies:

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); 

ich folgend in JavaScript (nicht von einer Strecke elegant):

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]); 
    } 
} 

Auch hier ist es nicht schön, aber nach dem Ausführen dieses Snippets stimmen die saltbytes und storedSubKeyBytes Byte für Byte überein, was ich im C# -Debugger für salt und storedSubkey sehe.

schließlich in C#, eine Instanz von Rfc2898DeriveBytes wird verwendet, um einen neuen Unterschlüssel zu erzeugen, auf dem Salz basierte und das Passwort zur Verfügung gestellt, etwa so:

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

Dies ist, wo ich bin stecken. Ich habe andere Lösungen wie this one versucht, ich habe CryptoJS und Crypto-Bibliotheken von Google und Node verwendet, und meine Ausgabe erzeugt nie etwas, das der C# -Version ähnelt.

(Beispiel:

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

erzeugt "LSJvaDM9u7pXRfIS7QDFnmBPvsaN2z7FMXURGHIuqdY =")

Viele der Zeiger I Online-Probleme hinweisen gefunden habe Beteiligung kodieren Mismatches (NodeJS/UTF-8 vs. .NET/UTF -16LE), also habe ich versucht, das Standardcodierungsformat von .NET zu verwenden, aber ohne Erfolg.

Oder ich könnte völlig falsch sein über das, was ich davon ausgehe, dass diese Bibliotheken tun. Aber alle Hinweise in die richtige Richtung würden sehr geschätzt werden.

+0

Versuchen Sie, einen Kennwort-Hash auf dem Client zu generieren und den Hash zur Validierung an den Server zu übergeben? – trailmax

+0

Nein, ich versuche, die Hash-Server-Seite in node.js zu generieren. Im Wesentlichen die Datenbank beibehalten, aber die IIS/asp.net-Schicht für den Knoten austauschen. Ich bin kein Sicherheitsexperte, aber ich würde vorsichtig sein, wenn ich versuche, die Client-Seite mit Passwort-Operationen zu betreiben. – GojiraDeMonstah

+0

Ah, das verdeutlicht meine Bedenken. Ich würde das in Ihrer Frage erwähnen. Sorry, kann nicht wirklich helfen mit JS-Seite der Dinge hier ( – trailmax

Antwort

9

Ok, ich denke, dieses Problem endete ein bisschen einfacher als ich es gemacht habe (sind sie nicht immer). Nachdem ich eine RTFM-Operation an der pbkdf2 spec durchgeführt habe, habe ich einige Side-by-Side-Tests mit Node-Krypto und .NET-Krypto durchgeführt und bin bei einer Lösung ziemlich gut vorangekommen.

Der folgende JavaScript-Code analysiert den gespeicherten Salt und den Unterschlüssel ordnungsgemäß und überprüft dann das angegebene Kennwort, indem er es mit dem gespeicherten Salt vergleicht. Es gibt zweifellos bessere/sauberere/sicherere Tweaks, also Kommentare willkommen.

// 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!"); 
} 
+2

Sie haben mir gerade das Leben gerettet Vielen Dank Ich migriere von ASP.NET zu node.js und jetzt muss ich meinen Benutzern nicht sagen, dass ihr Passwort abgelaufen ist: D – javorosas

+0

Wenn Sie mich lassen, werde ich einige Schlüsselwörter veröffentlichen, damit jemand wie ich dieses in der Zukunft einfacher finden kann: SimpleMembershipProviider Hash algorythm ASP.NET MVC Passwort Hashing vergleichen – javorosas

+0

Sicher Ding ..., was ich tun muss, um zu helfen, Posten die Schlüsselwörter? – GojiraDeMonstah

Verwandte Themen