Richtig hashen mit Node.js

Node.js-Module: bcrypt
Kommentare

Wenn es um das Speichern von Passwörtern geht, sind sich alle einig: Sie sollten niemals in Klartext abgelegt werden. Dann aber gehen die Meinungen auseinander: Welchen Hash-Algorithmus sollte man verwenden?

Hash-Algorithmen erzeugen für jede Eingabe eine nahezu eindeutige Ausgabe. Eine solche Hash-Funktion stellt eine nicht umkehrbare Operation dar. Das bedeutet, dass Sie in der Regel nicht von einem Passwort-Hash auf das eigentliche Passwort schließen können. Hashes kommen in Webapplikationen aus Sicherheitsgründen zum Einsatz. Gelingt es einem Angreifer beispielsweise, einzelne Passwort-Hashes auszuspähen, sind diese nur bedingt hilfreich, da er nicht an das Passwort gelangt.

Das Problem der meisten Hash-Funktionen ist aber, dass sie nicht für den Umgang mit Passwörtern entwickelt wurden. Die üblichen Verdächtigen wie MD5 und SHA1 sind so optimiert, dass sie für eine beliebige Eingabe möglichst schnell den Hash-Wert liefern. So können Sie mit diesen Funktionen beispielsweise Prüfsummen für Dateien oder Texte erzeugen. Eine schnelle Verarbeitung kommt jedoch auch einem Angreifer zugute, da er potenziell mehr Versuche in der gleichen Zeit unternehmen kann wie mit einem langsameren Algorithmus. Eine solche Brute-Force-Attacke ist durchaus üblich, um Passwörter zu knacken.

Um diese Nachteile der verbreiteten Hash-Funktionen zu umgehen, haben Niels Provos und David Mazières den bcrypt-Algorithmus entwickelt und ihn im Jahr 1999 der Öffentlichkeit präsentiert. Der große Unterschied zwischen bcrypt und anderen Hash-Funktionen ist, dass die Berechnung eines bcrypt-Hashes vergleichsweise aufwendig ist. Beim Hashen eines einzelnen Passworts stellt dies noch kein Problem dar. Wird allerdings gezielt versucht, Passwörter zu erraten, verlangsamt die Summe der Operationen das Gesamtsystem.

bcrypt und Node.js

Für Node.js existiert, wie für zahlreiche andere Plattformen und Programmiersprachen, ebenfalls eine Implementierung in Form eines npm-Pakets. Dieses können Sie mit dem Kommando npm install bcrypt in Ihrer Applikation installieren und dann zum Hashen Ihrer Passwörter verwenden. Das bcrypt-Paket basiert auf der C-Implementierung der Blowfish- und bcrypt-Algorithmen, was bedeutet, dass der Algorithmus nicht komplett in JavaScript umgesetzt ist, sondern auf die etablierten C-Bibliotheken zurückgegriffen wird.

Hashen von Passwörtern

Das bcrypt-Paket stellt Ihnen die hash-Funktion zur Verfügung, mit der Sie einen bcrypt Hash erzeugen können. Sie können die hash-Operation auf verschiedene Arten ausführen. Es existiert sowohl eine synchrone Version der Hash-Funktion mit dem Namen hashSync als auch eine asynchrone Version mit dem Namen hash. Die asynchrone Variante unterstützt Callback-Funktionen und Promises zur Behandlung des Ergebnisses. Die Entwickler des bcrypt-Pakets raten allerdings vom Einsatz der synchronen Version ab, da diese die Ausführung Ihrer Applikation blockiert – und das wird vor allem bei CPU-intensiven Algorithmen, wie es bcrypt ist, zum Problem. Listing 1 zeigt Ihnen die Verwendung der hash-Funktion an einem einfachen Beispiel.

const bcrypt = require('bcrypt');
const password = 'Top Secret';

bcrypt.hash(password, 10, (err, hash) => {
  if (err) {
    throw err;
  }
  console.log('Your hash: ', hash);
});

Der zweite Parameter der bcrypt-Funktion gibt die Laufzeitkosten des Algorithmus an. Das bedeutet für Sie: Je höher diese Zahl ist, desto ressourcenintensiver ist die Hash-Berechnung. Die hash-Methode in Listing 1 nutzt eine Callback-Funktion, um mit dem generierten Hash weiterzuarbeiten. Nutzen Sie diese Art, kann es sehr schnell zu tief ineinander verschachtelten Callback-Funktionen kommen. Eine schönere Option liegt in der Verwendung von Promises. Alles, was Sie hierfür tun müssen, ist, die Callback-Funktion wegzulassen und stattdessen die then-Methode des Rückgabewerts der hash-Methode zu verwenden. Dieser ist ein Promise-Objekt. Listing 2 zeigt, wie Sie das Beispiel umschreiben müssen.

const bcrypt = require('bcrypt');
const password = 'Top Secret';

bcrypt.hash(password, 10).then(
  hash => {
    console.log('Your hash: ', hash);
  },
  err => {
    console.log(err);
  }
);

Bei der bisherigen Verwendung der hash-Methode wurde das Salt innerhalb der Methode erzeugt. Sie können das Salt auch separat mit der genSalt-Methode erzeugen. Sie akzeptiert den gleichen Kostenwert wie die hash-Methode und generiert auf dieser Basis das Salt. Dieses Salt können Sie wiederum als zweites Argument an die hash-Methode übergeben. Sie weist – je nachdem, ob das zweite Argument eine Zahl oder eine Zeichenkette ist – ein unterschiedliches Verhalten auf. Bei einer Zahl wird intern die genSalt-Methode ausgeführt, um das Salt separat zu generieren. Für genSalt existieren wiederum eine synchrone und eine asynchrone Version, wobei Sie auch hier stets auf die asynchrone genSalt-Methode zurückgreifen sollten.

Vergleichen von Hashes

Will sich ein Benutzer an Ihrer Applikation anmelden, müssen Sie natürlich das Passwort und den gespeicherten Hash miteinander vergleichen, um festzustellen, ob der Benutzer wirklich derjenige ist, der er vorgibt zu sein. Beim Erledigen dieser Aufgabe hilft Ihnen die compare-Methode von bcrypt. Diese Methode ist, wie auch schon die hash-Methode, asynchron und unterstützt sowohl die Verwendung von Callback-Funktionen als auch Promises. Neben dieser Methode existiert außerdem die synchrone compareSync-Methode, die Sie jedoch auch nur in seltenen Fällen verwenden sollten, da auch sie Ihre Applikation potenziell blockieren kann. In Listing 3 sehen Sie, wie Sie ein Passwort überprüfen können.

bcrypt.compare(password, hash).then(
  result => {
    console.log('Submitted password is correct');
  },
  err => {
    console.log(err);
  }
);

Der compare-Methode übergeben Sie zunächst das Passwort in Klartext und als zweites Argument dann den Hash, der überprüft werden soll. Verwenden Sie eine Callback-Funktion für den Vergleich, prüft bcrypt anschließend beides auf Übereinstimmung und führt dann die übergebene Callback-Funktion mit einem Fehlerobjekt und einem booleschen Wert aus, der im Falle einer Übereinstimmung true ist.

In der Promise-Variante wird die Promise mit dem Wert true aufgelöst, falls Passwort und Hash übereinstimmen. Sollte beides nicht übereinstimmen, wird die Promise mit dem Wert false aufgelöst.

Fazit

Der Einsatz des bcrypt-Pakets in Node.js-Applikationen stellt eine relativ einfache und verhältnismäßig sichere Alternative zum Passwort-Hashing zur Verfügung. Natürlich löst die Verwendung von bcrypt alleine nicht alle Sicherheitsprobleme und sollte je nach Applikation mit weiteren Sicherheitsmechanismen kombiniert werden. Häufig sind jedoch restriktive Maßnahmen wie das Sperren von Nutzerkonten nach dem dritten Fehlversuch bei der Passworteingabe nicht umsetzbar. Gerade hier spielt dann bcrypt seine Stärke aus und bremst einen potenziellen Angreifer aus.

PHP Magazin

Entwickler MagazinDieser Artikel ist im PHP Magazin erschienen. Das PHP Magazin deckt ein breites Spektrum an Themen ab, die für die erfolgreiche Webentwicklung unerlässlich sind.

Natürlich können Sie das PHP Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -