Passpod - Secure Password Storage

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
ihucos
User
Beiträge: 8
Registriert: Dienstag 11. November 2014, 11:37

Hallo Python Forum!

ich habe folgendes gebastelt: https://github.com/ihucos/passpod
Die Hauptidee ist Benutzer-Passwörter nicht wie üblich in einem map aus Benutzernamen und gehashte Passwörter, sondern in einen set aus hashes abzulegen. Falls die Passwort Datenbank kompromittiert wird, soll der schaden geringer sein.

Scheint das für euch sinnvoll zu sein?

Feedback Willkommen!
apollo13
User
Beiträge: 827
Registriert: Samstag 5. Februar 2005, 17:53

Hi,

die erste Grundregel bei Crypto ist: Lass es jemanden machen der sich auskennt. Ganz grundsätzlich solltest du dir dazu einmal http://pythonhosted.org//passlib/ anschauen. Im Normalfall will man auch zb einen extra salt, sowie den algo und dessen paramter im Hash haben um ein Upgrade zu ermöglichen. Das geht aber alles nicht wenn du null Verbindung zu einem User hast, insofern würde ich sagen ist die fehlende Verbindung zu einem Userobjekt eine sehr sehr schlechte Idee.

Und nur um auf einen grundlegenden fundamentalen Fehler hinzuweisen: Wenn jemand deine Datenbank und deinen Code in die Hände bekommt, dann war dein SLOWNESS Parameter absolut wertlos. Schau dir dazu mal PBKDF2 an und überleg wo das Problem in deinem Code ist und was PBKDF2 (oder bcrypt/scrypt und andere) da richtig machen.

Cheers,
Florian
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@ihucos: Dein Hash-Algorithmus ist wirklich schlecht, da solltest Du konfigurierbar eine externe Bibliothek einbinden. Die Sache mit den Revisionen verstehe ich nicht. Vielleicht kannst Du die ganze Idee dahinter mal genauer erklären. Was sind Deine Angriffsszenarien und wie schützt Du Dich dagegen?
Wenn Du eigentlich kein Dictionary schreiben willst, warum verwendest Du dann ein so kaputtes Interface? Ein Funktion check_credentials wäre da um einiges klarer.
Normalerweise sind ja hinter einem Username noch Email, Klarname, etc. gespeichert. Daraus läßt sich wahrscheinlich mit hoher Trefferquote ein Benutzername ableiten, ansonsten hilft eine Tabelle mit den 1000 häufigsten Usernamen, und das Knacken von Username + Passwort ist dann kaum komplexer als nur ein Passwort.
ihucos
User
Beiträge: 8
Registriert: Dienstag 11. November 2014, 11:37

@apollo13
Ich kenne passlib. Das man den Hash Algorithmus und Saltz nicht einfach ändern kann ist auf jeden Fall eine großer Nachteil.
Wenn jemand deine Datenbank und deinen Code in die Hände bekommt, dann war dein SLOWNESS Parameter absolut wertlos. Schau dir dazu mal PBKDF2 an und überleg wo das Problem in deinem Code ist und was PBKDF2 (oder bcrypt/scrypt und andere) da richtig machen.
Hmmm, ich komme nicht auf was du meinst, sag es mir bitte.

@Sirius3
Dein Hash-Algorithmus ist wirklich schlecht, da solltest Du konfigurierbar eine externe Bibliothek einbinden
Ja ich weis, ich habe extra den schlechtesten genommen um zu zeigen das es nicht um den eigentlichen hash Algorithmus geht, da ist auch schon ein FIXME
Wenn Du eigentlich kein Dictionary schreiben willst, warum verwendest Du dann ein so kaputtes Interface? Ein Funktion check_credentials wäre da um einiges klarer.
Einverstanden, dict-like ist doch kein gutes Interface dafür.
Normalerweise sind ja hinter einem Username noch Email, Klarname, etc. gespeichert. Daraus läßt sich wahrscheinlich mit hoher Trefferquote ein Benutzername ableiten, ansonsten hilft eine Tabelle mit den 1000 häufigsten Usernamen, [...]
Vorausgesetzt der hash ist auch kompromittiert, wenn nur zugriff auf die Datenbank besteht können die Benutzernamen nicht ohne weiteres gebrute forced werden.
[...] und das Knacken von Username + Passwort ist dann kaum komplexer als nur ein Passwort.
Es ist schon eine weile her das ich Passpod geschrieben habe, aber ich glaube ich habe jetzt wieder was ich mir dabei gedacht habe. Und zwar nein, es ist nicht so unglaublich viel komplexer aber erfordert viel mehr Speicher Ressourcen. Die idee ist das man mit `add_dirt` vorher sagen wir 10GB "falsche" hashes in der Datenbank schreibt. Was brute force verlangsamen soll ist nicht das eigentlichen hashen, sondern das entweder die 10GB an hashes einzeln gebrute forced werden muessen oder der "normale" weg ueber die Abstraktionsschicht von Passpod (`.has(item)`): der geratene Benutzername und Password wird gehasht und jetzt muss überprüft werden ob dieser hash der selbe ist wie irgeindeiner von denen in den 10GB. Beides sollten für GPUs und ASICs schwierig sein (oder?)

Ich weis das klingt vielleicht verwirrend, ich versuche das nochmal etwas ausfuerlicher zu beschreiben: Die Funktionalitaet von Passpod baut auf eine Datenbank auf die maximal minimalistisch ist und nur zwei Methoden braucht, nämlich `add` und `has`. Also ein objekt speichern (wird json gecoded und dann gehasht) und überprüfen ob ein Objekt gespeichert wurde. Benutzername-Passwoert mappings werden als folgendes tuple abgespeichert: ('key-value-pair', revision, user, password). Das `revision` koennen wir ignorieren das wird einfach eine kleine nummer sein, naemlich wie oft das passwort geandert wurde. So, jetzt nehmen wir folgendes angriffszenario an: ein Angreifer hatte fuer kurze Zeit zugriff auf das gesamte System, inklusive die Passwort-Datenbank und den verwendeten Salz. Wogegen Passpod vor allem hilft sind gezielte angriffe gegen einen account. Normalerweise haette ein angreifer mit (amazon cloud) GPUs oder ASICs relativ gute chancen das richtige passwort zu raten oder zumindestens einen guten "Hebel" weil die Server CPU verhaeltnismaessig langsam hasht und so die anzahl an iterationen von immer wieder den vorigen hash hashen verhaeltnismaessig gering sein wird. Bei Passpod soll das anders sein, die Datenbank ist ein set aus sagen wir 10GB an hashes. Jedes hash representiert ein Objekt das in der datenbank drinn ist. Fuer jedes ausprobierte passwort muss ein angreifer diese 10GB irgendwie durchkauen da es ja keine direkte verlinkung zu Benutzernamen und Passwort besteht. Konkret bedeutet das man muesste eine Datenbank mit den hashes bauen und fuer jedes Passwort schauen ob das entsprechende User-Password mapping drinne ist (also `(..., 'user7', 'aaa')`, `(..., 'user7', 'aab')`, etc). Solche Datenbanksachen ist im gegensatz zu Hashen genau was server gut koennen. Zusaetzlich kann so (siehe `slow_add`, `slow_has`, `SLOWNESS` im code) die Resourcen die fuer einen Brute Force Angriff notwendig sind weiter erhoet werden. Natuerlich kann auch jeder Hash in der Datenbank einzeln angegriffen werden, daran hatte ich nicht gedacht, aber pi mal daumen halte ich das auch in naechster Zeit fuer nicht praktikabel.

Die Grundidee ist die Schnelligkeit von brute force angriffen vom eigentlichen hashing Algorithmus unabhaengig zu machen. Und gleichzeitig auch die Informationen die eine kompromitierte Passwort Datenbank preisgibt moeglichst klein zu halten.


lg,
ihuecos
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@ihucos: also wenn ich das richtig sehe, hast Du eine Tabelle, in der nur der Key gehasht mit einer ganzen Reihe aufsteigender Zahlen hast. Damit dürfte es wohl relativ einfach sein, Usernamen herauszufinden.
Den Sinn dieser Revision sehe ich damit immer noch nicht, nur dass sie Dein System schwächt.
Ob man 10GB an Hashes oder 100GB hat, ist relativ egal. Auch beim Cracken wird aus einem Username/Passwort ein Hash berechnet und verglichen, ob der in den Datensätzen ist, was sehr effizient mit einem Index gemacht werden kann.
ihucos
User
Beiträge: 8
Registriert: Dienstag 11. November 2014, 11:37

@Sirius3
Haha, nein.
Ich habe echt schon sehr oft versucht das im RL zu Erklaeren, erfolglos.
Ok, letzter versuch:
Die revision kann man fuer das gesamtkonzept erstmal komplett vernachlaesigen, kurz was sie macht:
In der Datenbank koennen Eintraege nur reingeschrieben, aber nicht geloescht werden. Einfach gesagt brauchen deswegen die Benutzername-Passwort Datenbank Eintraege eine revision um von der Benutzer perspektive aenderbar zu sein. Jedes mal wenn ein Benutzer sein Password aendert wird die Revision fuer den neuen Eintrag incrementiert. Beim validieren eines Passworts wird das Benutzername-Password Mapping Eintrag mit der hoechsten Revision benutzt.

Auch wenn was brute force verlangsamen soll, naemlich zugriffe ueber einen Index auf einige GB an hashes sehr schnell ist, gehe ich davon aus das es deutlich langsamer ist als hashen, vor allem fuer GPU und ASICs (oder?)

Ok, ich verusche mal eine vereinfachte Version von Passpod zu erklaeren.
Benutzername und Password werden mit einem leerzeichen zusammengesetzt, gehasht und dann in einen persistenten Set gespeichert. In diesen Set befinden sich uebrigens auch mehrere GB an anderen hashes.
Wenn wir jetzt ueberpruefen wollen ob eine Benutzername-Password Kombination richtig ist, hashen wir es nach der genannten Methode und schauen nach ob sich dieser Hash in der Datenbank befindet. Passpod ist nichts weiter als das, dass ganze SLOWNESS, revision oder dict-like Interface sind nur Implementierungsdetails die wir vernachlaessigen koennen.

Der grund warum ich glaube, dass das besser als die herkoemmliche Methode, naemlich password mit PBKDF2/crypt/etc gehasht direkt auf einen Benutzernamen verlinkt sein keonnte ist folgendes:
Ein Angreifer muesste beim brute force ueberpruefen ob sich ein hash in einen grossen set befindet, statt auf schnelles hashes wuerde es auf diese operation ankommen. Ich gehe davon aus das herkoemliche Server hier nicht so einen grossen Nachteil haben wie beim hashen im vergleich zu ASICs. Vor allem angesichts der foranschreitenden Entwicklung von Anreizen wegen Crypotwaerungen das Hashen zu optimieren.

Wenn das weiterhin Sinnfrei klingt kommt meine Idee wahrscheinlich erstmal in der Schublade. Ich sollte vielleicht die Idee besser validiert haben bevor ich den prototyp geschrieben habe.

lg,
ihucos
BlackJack

@ihucos: Ich verstehe nicht wo da genau der Vorteil sein soll. Das einzige ist ja dass man den Benutzernamen raten muss und damit nur welche knacken kann die einen verbreiteten Nutzernamen verwenden. Ob ein Hash in einem grossen Set von Hashes enthalten ist, da gehe ich jetzt mal davon aus, dass das ein halbwegs effizienter Test ist. Insbesondere für Leute die daran interessiert sind Passworte zu knacken.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@ihucos: einen Hash zu finden ist eine um Größenordnungen schnellere Operation als einen Hash zu bilden, weil vor allem die Weiterentwicklung von Hash-Algorithmen darauf abzielt, dass die Geschwindigkeit des Hashens bei steigender Rechenleistung konstant bleibt (also immer mehr Iterationen).

Und nochmal: Deine Revisionstabelle vereinfacht das finden von gültigen Usernamen per brute force enorm, da es nicht mehr nötig ist, Username+Passwort zu finden. Warum ist das Löschen von Einträgen nicht möglich?
ihucos
User
Beiträge: 8
Registriert: Dienstag 11. November 2014, 11:37

Und nochmal: Deine Revisionstabelle vereinfacht das finden von gültigen Usernamen per brute force enorm, da es nicht mehr nötig ist, Username+Passwort zu finden. Warum ist das Löschen von Einträgen nicht möglich?
Ich verstehe nicht ganz was mit Revisionstabelle gemeint ist. Einzelne benutzer werden abgespeichert damit man beim anlegen von passwoertern ueberpruefen kann ob der enstprechende benutzer nicht schon existiert und um die entsprechende revisionsnummer zu inkrementieren und fuer den neuen Eintrag zu benutzen.

Das Loeschen von Eintraegen ist nicht moeglich um die implementierung einfach zu halten und wegen der Fehlentscheidung die ganze Funktionalitaet in eine art Subset von dictionaries zu haben. Es waere moeglich, allerdings nur wenn der Eintrag bekannt ist, also sprich der Benutzer sein Passwort nicht vergessen hat.

Ob ein Hash in einem grossen Set von Hashes enthalten ist, da gehe ich jetzt mal davon aus, dass das ein halbwegs effizienter Test ist.
@ihucos: einen Hash zu finden ist eine um Größenordnungen schnellere Operation als einen Hash zu bilden, weil [...]
Genau das ist der Kernpunkt von Passpod.

Stimmt, Hashen ist langsamer als ein Key-Value access, auf einer CPU.

Code: Alles auswählen

In [19]: d = dict((i, None) for i in range(1000000))
In [20]: timeit d[0]
10000000 loops, best of 3: 45.6 ns per loop

In [24]: timeit hashlib.sha1('A'*12).digest()
1000000 loops, best of 3: 815 ns per loop
Das spielt aber keine rolle, es ist trivial etwas analoges zu den Iterationen beim hashen zu machen (siehe `slow_add` und `slow_has` im code). So braucht man um das Vorhandensein eines Hashes im Set zu ueberpruefen beliebig viele "ist der hash im set drinn" Operationen. Mein Punkt ist das man so gegen spezialisierte Hardware besser aufgestellt ist (niemand brute forced mit der CPU). Ich glaube die Frage ist ob key-value-access "nauterlicherweise" schlecht auf GPU/FPGA/ASIC laeuft. Wenn das so ist (ist es so?) kann ein Server mit der Arbeit die er in der er das Einwegverschlüsseln von Passwoerten steckt mit spezialisierter Hardware mithalten (was bei "normales" hashen nicht der Fall ist.)

EDIT:
Key-Value-Access ist nicht ganz richtig, wie heist das, indexierter zugriff? includes-operation?
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@ihucos: es müßte wohl richtigerweise Revisionentabelle heißen, also die Tabelle wo quasi der Username als Hash alleine drin steht. Dann muß ich nicht noch alle Passwörter durchprobieren. Und da Usernamen im Allgemeinen keine kryptischen Zeichen enthalten, kann ich mich auf Kleinbuchstaben + Zahlen beschränken, um einen Großteil der Usernamen zu cracken.

Eine Prüfung ob ein Element in einem Set ist, dürfte bei einer Milliarde Einträgen bei ungefähr 5 Operationen liegen, wenn man eine sortierte Liste vorliegen hat und der Hash-Algorithmus den Zahlenraum gleichmäßig abdeckt.
Auf die Lösung, wie sich das trivial Verhindern läßt, bin ich gespannt :P.
ihucos
User
Beiträge: 8
Registriert: Dienstag 11. November 2014, 11:37

Eine Prüfung ob ein Element in einem Set ist, dürfte bei einer Milliarde Einträgen bei ungefähr 5 Operationen liegen, wenn man eine sortierte Liste vorliegen hat und der Hash-Algorithmus den Zahlenraum gleichmäßig abdeckt.
Auf die Lösung, wie sich das trivial Verhindern läßt, bin ich gespannt :P.
So (https://github.com/nomoral/passpod/blob ... od.py#L102):

Code: Alles auswählen

...
    def has(self, message):
        # print 'has', message
        message_hash = self._hash(message)
        stmt = select([1]).where(self._table.c.hash == message_hash).limit(1)
        return bool(self._engine.execute(stmt).scalar())

    def slow_add(self, message):
        self.add((message, random.randint(0, self.SLOWNESS)))

    def slow_has(self, message):
        for i in range(0, self.SLOWNESS):
            if self.has((message, i)):
                return True
        return False
...
Ich sehe aber schon das Passpod nicht so funktioniert wie ich mir das gedacht habe :-/
danke fuers Feedback!

lg,
Ihuecos
apollo13
User
Beiträge: 827
Registriert: Samstag 5. Februar 2005, 17:53

Das spielt aber keine rolle, es ist trivial etwas analoges zu den Iterationen beim hashen zu machen (siehe `slow_add` und `slow_has` im code).
Offensichtlich nicht so trivial, denn `slow_add`/`slow_has` sind ziemlich nutzlos -- gerade auf einer GPU kann ich dir massig von denen gleichzeitig ausrechnen. Ein iteratives Verfahren, welches nicht die vorherigen Werte mitverwendet bringt so gut wie null Sicherheitsgewinn.
Antworten