Password-Handling

    • VB.NET

    Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      Password-Handling

      Hi!

      Ich hab hiermal ein proof of concept gemacht, v.a. um 2 Dinge zu zeigen:
      1. zunächst mal das Konzept selbst
        Zum sicheren Abspeichern von UserDaten gilt folgender Standard:
        Passworte werden nicht gespeichert, sondern aus dem PW wird ein Hash gebildet, der gespeichert wird. (Dieser Vorgang ist unumkehrbar - Aus einem Hash kann nicht das Wort zurück-errechnet werden). Zur Identifikation wird das eingegebene Wort ebenfalls "verhasht", und wenn die beiden Hashes übereinstimmen - ok.
        Dieses Konzept ist selbst dann sicher, wenn die gesamte LogIn-Datenbank geklaut ist. Denn der Dieb hat damit nur Hashes - keine Passwörter :P .
        Ein Hash aus einem Wort ist aber nicht sicher genug, denn Hacker benutzen u.U. riesige sog. "RainbowTables", in denen alle denkbaren Worte und ihre Hashes nachgeschlagen werden können.
        Daher wird zusätzlich zum PW eine (hier: 16-stellige) Zufalls-ByteFolge generiert, das "Salt", und beim Verschlüsseln mit dem PW verknüpft. Obwohl das Salt unverschlüsselt gespeichert wird, sind dadurch die RainbowTables ausgebootet, denn nun müssten sie die 2^16 - fache Menge an Einträgen vorhalten, und das wirdn bischen ungemütlich, denn die RainbowTables sind eh schon im Gigabyte-Bereich ;) - gugge Wiki: RainbowTable

      2. Überflüssigkeit des Benutzernamens zur Identifikation:
        Hier befund ich mich im Irrtum: Auch wenns lästig ist, zur Identifikation Passwort und UserName anzugeben, ist es erforderlich, jdfs. wenn man dem User Wahlfreiheit bezüglich des Passwortes gewähren will.
        Denn bei LogIn/Registration ohne Username kann vorkommen (insbesondere bei schwachen PWs) dass ein User ein bereits vergebenes PW registrieren möchte, was logischerweise abgelehnt wird (kein Problem).
        Das Problem daran ist, dass die Ablehnung seiner Registrierung ihn gleichzeitig darüber informiert, dasser auf die Zugangsdaten eines anderen Users gestoßen ist.
        Hier zeigt sich die Konstruktion mittm Username zusätzlich zum Passwort als notwendig, denn nun kann die Registrierung bereits den Usernamen ablehnen, wodurch ja nur die Hälfte der Identifikation des anderen Users preisgegeben ist.
        Ein PW-Handling ohne Benutzernamens-Pflicht dürfte dem User also keine Wahlfreiheit bezüglich des PWs zugestehen, sondern würde ihm bei Registrierung ein generiertes Passwort zuweisen. Wäre eine Überlegung wert, denn dabei könnte nebenbei auch sichergestellt werden, dass überhaupt nur starke PWs in Umlauf sind.
        Hätte andererseits wieder den Nachteil, dass man sich starke PWs ohne Hilfsmittel gar nicht merken kann - und wie sicher sind die Hilfsmittel, die ein User sich zurechtmacht: Zettel? besondere PW-Datei? Passwort-Manager?

      3. Berechtigungs-Erteilung ohne Benutzername
        Natürlich gibt es weiterhin auch Anwendungsfälle, wo ein Username zur Erteilung einer Zugriffsberechtigung entbehrlich ist. Nämlich Anwendungen, die eh nur von einem User zu nutzen sind, bzw. Dokumente (vgl. den Passwortschutz von Office-Dokumenten). Darunter kann auch der administrative Zugriff auf eine Anwendung fallen, nämlich wenn nur ein Admin vorgesehen ist, bzw. die Admins nicht als Personen unterschieden werden. Aber auch ganze Datenbanken schützen sich durch nur ein Passwort - fassen sich also gewissermaßen als ein großes Dokument auf.
        Jdfs. jetzt habichnoch ein Projekt drangemacht, was ein Password-Handling für Einzel-User-Anwendungen/Dokumente implementiert.

      Umsetzung:
      • Multi-User-App: Zunächst öffnet ein Login/Register - Form, mit dem man sich entweder einloggen oder registern kann - je nachdem, welchen BestätigungsButton man klickst.
        Daraufhin kriegt man seine Daten präsentiert - mehr macht die Sample-App nicht.
        Man kann natürlich seine Identifikation auch ändern.
        Die eigentlichen User-Daten bestehen aus einem eigenständigen Dataset, welches als ganzes verschlüsselt und als Byte-Array im User-Datensatz abgelegt wird.
        Also auch ein Admin hat keine Chance - darauf komm ich noch zurück.
        Da es ein komplettes Dataset ist, was verschlüsselt wird, ist der Ansatz hochflexibel - eigentlich jede Art und Menge an Userdaten kann man reinpacken - ganze Datenbanken.

      • Single-User-App: Zunächst gibt man in eine Inputbox das Passwort ein.
        Ist ein DatenFile vorhanden, wird das PW geprüft, und im Erfolgsfall die entschlüsselten Daten präsentiert.
        Ist kein DatenFile vorhanden, wird ein mit dem angegebenen PW geschütztes File angelegt.
        Passwortwechsel ist ebenfalls vereinfacht, im Vergleich zur Multi-User-App.



      Das Hochsicherheits-Konzept der Multi-User-App ist allerdings nur begrenzt praxistauglich
      Was soll ein User mit so geheimen Daten, dass die Anwendung selbst nichtmal damit interagieren kann?
      Kanner Tagebuch schreiben - toll! Ein richtig sicheres Tagebuch-Programm für die ganze Familie. (OffTopic: Tagebücher wollen doch eiglich iwann einmal gefunden werden - odr? :))
      Aber ein Versandhandel könnte zB keine Email schreiben an einen Kunden, dessen Email durch sein indiv. Passwort so abgesichert ist.
      Oder die User eines Online-Games könnten einander nicht wahrnehmen, weil der Server vom jeweils anneren nur Crypt-Daten rausrücken kann.
      Also prinzipiell muß meißt auch ein zusätzlicher, "öffentlicher" UserDaten-Bereich her, der weniger strikt geschützt ist.

      Wozu isses Sample gut?
      Na, ihr könnt mal schauen, wie man ein Salt generieren kann, einen Crypto-Hash erstellen, oder auch einen Crypto-Stream. Dabei verwende ich nirgends einen selbstgebastelten Algorythmus, sondern ausschließlich die im Framework vorgesehene Technologie, von der anzunehmen ist, dass sie den aktuellen Stand der Kryptografie-Wissenschaft korrekt umsetzt.

      Schon die Logik zu verstehen ist nicht ganz trivial, findich. Ich hatte mich ja böse verhauen, weil mir der Benutzername als eigentlich unsinnige Verlängerung des Passwortes vorkam. Als Benutzername ist er tatsächlich nicht erforderlich, aber man braucht eine Teil-Identifikation, deren Registrierung man ablehnen kann, ohne damit die vollständige Identifikation eines anderen Users preisgegeben zu haben.
      Auch muß man verhashen und verschlüsseln auseinanderhalten: Ein Passwort wird verhasht - und zwar, nachdem es durch Zugabe des Salts zusätzlich verkompliziert wurde. Etwas anderes ist es, aus Passwort und Salt einen Key zu berechnen, mit dem NutzDaten schließlich auch ver-/ent-schlüsselt werden.
      Weiters sollte man sich davon trennen sollte, bei Kryptografie an Strings zu denken.
      Ein Schlüssel ist ein Byte-Array - kein String. Und man verschlüsselt auch keine Texte, sondern es wird ausschließlich mit Byte-Arrays bzw. Streams hantiert. Ob die Bytes nun Text bedeuten oder Datensätze, Binär-Codes oder Bilder ... - die Interpretation der Daten geht die Kryptografie schon nix mehr an.
      Und prinzipiell sind immer 3 Anforderungen zu implementieren: Registration, LogIn und Ändern von Identifikations/Passwort.

      Implementation
      Ich habe mir 2 kryptografische Hilfsklassen gebastelt, die einerseits die unendlichen Möglichkeiten, Kryptografie zu konfigurieren einschränken, dafür aber auch einfacher zu handhaben sind. Denn für eine Anwendung wählt man sicherlich auch nur eine Art der Konfiguration aus, und die ist somit an einer Stelle im Code fest verdrahtet.
      • Crypter: Zuständig für das Zusammenspiel von Passwort, Salt und Passwort-Hash, sowie für die Ver-/Ent-schlüsselung eines anzugebenden Streams. Verwendet den SHA256-Algorythmus zum verhashen, mit einem 16 Byte-Salt

      • AesStream: ein CryptoStream-Erbe, der AES-Verschlüsselung verwendet
      Hier die Crypter-Klasse, ich hoffe hinreichend kommentiert:

      VB.NET-Quellcode

      1. Imports System.Text
      2. Imports System.IO
      3. Namespace System.Security.Cryptography
      4. ''' <summary>
      5. ''' verwaltet UserName, Salt und Password. Generiert aus Salt und Password einen Hash.
      6. ''' Erzeugt einen CryptoStream, der aus einem (unverschlüsselten) Stream liest bzw. hineinschreibt
      7. ''' </summary>
      8. Public NotInheritable Class Crypter
      9. Public ReadOnly Salt(15) As Byte
      10. Private _ByteBuffer(1023) As Byte
      11. Private _Sha As New SHA256Managed
      12. Private _Random As New RNGCryptoServiceProvider
      13. Public Username, Password As String
      14. Public Function GetHash() As Byte()
      15. 'schreibt das Passwort hinter das Salt in den Puffer, computet aus beiden den Hash
      16. Dim pwLength = Encoding.ASCII.GetBytes(Password, 0, Password.Length, _ByteBuffer, 16)
      17. Return _Sha.ComputeHash(_ByteBuffer, 0, pwLength + 16)
      18. End Function
      19. Public Sub CreateSalt()
      20. _Random.GetBytes(Salt) 'generiert eine starke Zufallsfolge ins Salt-Array
      21. Array.Copy(Salt, _ByteBuffer, 16) 'kopierts Salt in den Puffer
      22. End Sub
      23. Public Sub SetSalt(ByVal value As Byte())
      24. Array.Copy(value, Salt, 16) 'kopiert value ins Salt-Array
      25. Array.Copy(Salt, _ByteBuffer, 16) 'kopierts Salt in den Puffer
      26. End Sub
      27. Public Function CreateCryptoStream(ByVal strm As Stream, ByVal encrypt As Boolean) As CryptoStream
      28. If encrypt Then Return New AesWriteStream(strm, Password, Salt)
      29. Return New AesReadStream(strm, Password, Salt)
      30. End Function
      31. End Class
      32. End Namespace
      Interessant vlt., dass Salt auf 3 Weisen Public zugreifbar ist:
      • Die Readonly Salt-Variable ist erforderlich, ums Salt abzurufen, wenn es zusammen mit dem Password in einem Datensatz abgelegt werden soll.
      • SetSalt() braucht man, um aus einem eingelesenen Datensatz einen Crypter richtig einzustellen.
      • CreateSalt() braucht man, wenn ein neuer Datensatz angelegt, oder das Passwort gewechselt wird.

      Zu beachten
      • Die Solution (alias "Projektmappe") enthält 3 Projekte. Also startet sie nicht über eine der Projekt-Dateien (Endung ".vbproj"), sondern über die Solution-Datei (".sln")
      • Als erstes die Solution erstellen (= kompilieren). Andernfalls kann der Form-Designer u.U. bestimmte Forms nicht anzeigen, weiler die dll des Helfer-Projektes benötigt, welches halt nur als Source vorliegt, solange noch nicht kompiliert ist.
      • Um die Single-User-App auszuprobieren muß man per Kontextmenü im Solution-Explorer das "DocumentPW"-Projekt als Startprojekt einstellen

      Update 11.6.11
      Ich habe das Anwendungs-Design vereinfacht, indem ich das Salt einfach dem gehashten Password voranstelle. Funktional ist das kein Unterschied, aber die Datenhaltung ist erleichtert, weil nur noch ein Byte-Array zur Authorisierung vorgehalten werden muß, und das Gefummel mittm Salt ist vollständig aus der Anwendung heraus, und in der Crypter-Klasse verkapselt:

      VB.NET-Quellcode

      1. Public Class Crypter
      2. Private _SaltedHash() As Byte = Nothing
      3. Private _Sha As New SHA256Managed
      4. Private _Random As New RNGCryptoServiceProvider
      5. Public Username As String
      6. ''' <summary>erzeugt einen Password-Hash mit vorangestellten 16 Byte Salt</summary>
      7. Public Function RegisterPassword(ByVal pw As String) As Byte()
      8. Dim salt(15) As Byte
      9. _Random.GetBytes(salt) 'generiert eine starke Zufallsfolge ins Salt-Array
      10. Dim bytes = ConcatArray(salt, Encoding.ASCII.GetBytes(pw)) 'verknüpft salt und Passwort
      11. Dim hsh = _Sha.ComputeHash(bytes) 'errechnet aus beiden den Hash
      12. _SaltedHash = ConcatArray(salt, hsh) 'stellt das salt voran
      13. Return _SaltedHash
      14. End Function
      15. Public Function VerifyPassword(ByVal pw As String, ByVal saltedHash As Byte()) As Boolean
      16. Dim saltHash = SplitArray(saltedHash, 16) 'trennt salt und hash
      17. Dim testBytes = ConcatArray(saltHash(0), Encoding.ASCII.GetBytes(pw)) 'verknüpft salt und Passwort
      18. Dim testHash = _Sha.ComputeHash(testBytes) 'errechnet aus beiden den Hash
      19. VerifyPassword = saltHash(1).EachEquals(testHash) 'test auf gleichheit jedes bytes
      20. Me._SaltedHash = If(VerifyPassword, saltedHash, Nothing)
      21. End Function
      22. Public Function CreateCryptoStream(ByVal strm As Stream, ByVal encrypt As Boolean) As CryptoStream
      23. If _SaltedHash.Null Then Throw New UnauthorizedAccessException( _
      24. "there is no password registered or verified")
      25. Dim transform = CreateTransform(encrypt)
      26. Dim mode = If(encrypt, CryptoStreamMode.Write, CryptoStreamMode.Read)
      27. Return New CryptoStream(strm, transform, mode)
      28. End Function

      vlt. erkennbar: Man kann ein Passwort registrieren (Rückgabe: ein "SaltedHash") oder verifizieren, und sich danach(!) einen CryptoStream abholen

      Dieser Beitrag wurde bereits 11 mal editiert, zuletzt von „ErfinderDesRades“ () aus folgendem Grund: Downloads entfernt - s. post#8

      ErfinderDesRades schrieb:

      Eine Datenbank solltewohl imstande sein, anhand eines Passwortes den Benutzer zu identifizieren

      NEIN!
      Benutzername und Passwort sind pflicht! :cursing:

      Was glaubst du du hast jetzt eine "riesiges Forum" (Facebook, Twitter, usw.) und diese verlangen nur das PW!
      Da gibt einer (mehr oder weniger) zufällig ein falsches Passwort ein und schon ist er bei 'nem anderem drin! :sleeping:
      Oder was ist wenn es mehrere User mit dem selben Passwort gibt? ^^ Ist ja auch möglich.
      (Würdest du dann beim Registrieren sagen, dass das PW schon vorhanden ist, könnte der sich mit einem schon vorhandenen Account einloggen :P )

      Also: Völliger Blödsinn :thumbdown:
      Das einige was du machen kannst, ist den Benutzernamen zu speichern und ihn beim nächsten Start wieder abzurufen :thumbup:
      das mit salt verschlüsseln hilt dir aber nicht wenn durch eine lücke jemand n backup deiner db macht und die runterläd da steht der salt drin

      dann sucht man sich nur den admin ( meist id1 oder einfach in der teamliste die id ansehen ) und weis die userid

      somit habe ich : salt + verschlüsselte pw :D siehe anhang #4 und #5 ist aus dem aktuellen wbb3

      am sinnvollsten ist : sicheres coden ;) wie vb-paradise.de/user/8219-memo/ schon schrieb
      Bilder
      • wbb user tabel demo.png

        85,57 kB, 938×798, 535 mal angesehen

      Memo schrieb:

      Da gibt einer (mehr oder weniger) zufällig ein falsches Passwort ein und schon ist er bei 'nem anderem drin!

      Jo, da haste recht.
      werde demnächst die Benutzer-Namerei mit einbauen.



      triple-Axe schrieb:

      das mit salt verschlüsseln hilt dir aber nicht wenn durch eine lücke jemand n backup deiner db macht und die runterläd da steht der salt drin

      dann sucht man sich nur den admin ( meist id1 oder einfach in der teamliste die id ansehen ) und weis die userid
      verstehe ich nicht - Habichda auch was falsch gemacht, im Umgang mit dem Salt? Das habich von wikipedia übernommen, dass man das salt unverschlüsselt mit dem PW-Hash abspeichert.

      Und, (angenommen eine nachgebesserte Benutzernamerei) - was nützt einem Hacker, der an die Benutzerdaten will, das Admin-PW zu knacken?
      Damit hatter die Admin-Daten, aber nicht die Benutzerdaten. Jdfs, wie ichs hier gemacht habe.
      Aber wie gesagt: ist ja auch problematisch, denn wenn wirklich nur der Benutzer an die Benutzerdaten kann, können die benutzerdaten eben auch nur sehr eingeschränkt im Sinne des Benutzers verwenden - die Daten benötigt man ja, um iwelche Dienstleistungen für ihn zu erbringen.
      ich habs nochmal neu gemacht, weil so wie der Crypter war, war das nicht so schön, dass derselbe Passwort-Hash zu Autentifizierung und Verschlüsselung hergenommen wurde.
      Weil da brauchte man dann kein Passwort mehr zum Entschlüsseln, wenn der Passwort-Hash direkt abgerufen wern kann.

      8|

      gugge [VB 2010] Verschlüsseln und Autentifizieren