Hi!
Ich hab hiermal ein proof of concept gemacht, v.a. um 2 Dinge zu zeigen:
Umsetzung:
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.
Interessant vlt., dass Salt auf 3 Weisen Public zugreifbar ist:
Zu beachten
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:
vlt. erkennbar: Man kann ein Passwort registrieren (Rückgabe: ein "SaltedHash") oder verifizieren, und sich danach(!) einen CryptoStream abholen
Ich hab hiermal ein proof of concept gemacht, v.a. um 2 Dinge zu zeigen:
- 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 .
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
Ü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?
- 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
VB.NET-Quellcode
- Imports System.Text
- Imports System.IO
- Namespace System.Security.Cryptography
- ''' <summary>
- ''' verwaltet UserName, Salt und Password. Generiert aus Salt und Password einen Hash.
- ''' Erzeugt einen CryptoStream, der aus einem (unverschlüsselten) Stream liest bzw. hineinschreibt
- ''' </summary>
- Public NotInheritable Class Crypter
- Public ReadOnly Salt(15) As Byte
- Private _ByteBuffer(1023) As Byte
- Private _Sha As New SHA256Managed
- Private _Random As New RNGCryptoServiceProvider
- Public Username, Password As String
- Public Function GetHash() As Byte()
- 'schreibt das Passwort hinter das Salt in den Puffer, computet aus beiden den Hash
- Dim pwLength = Encoding.ASCII.GetBytes(Password, 0, Password.Length, _ByteBuffer, 16)
- Return _Sha.ComputeHash(_ByteBuffer, 0, pwLength + 16)
- End Function
- Public Sub CreateSalt()
- _Random.GetBytes(Salt) 'generiert eine starke Zufallsfolge ins Salt-Array
- Array.Copy(Salt, _ByteBuffer, 16) 'kopierts Salt in den Puffer
- End Sub
- Public Sub SetSalt(ByVal value As Byte())
- Array.Copy(value, Salt, 16) 'kopiert value ins Salt-Array
- Array.Copy(Salt, _ByteBuffer, 16) 'kopierts Salt in den Puffer
- End Sub
- Public Function CreateCryptoStream(ByVal strm As Stream, ByVal encrypt As Boolean) As CryptoStream
- If encrypt Then Return New AesWriteStream(strm, Password, Salt)
- Return New AesReadStream(strm, Password, Salt)
- End Function
- End Class
- End Namespace
- 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
- Public Class Crypter
- Private _SaltedHash() As Byte = Nothing
- Private _Sha As New SHA256Managed
- Private _Random As New RNGCryptoServiceProvider
- Public Username As String
- ''' <summary>erzeugt einen Password-Hash mit vorangestellten 16 Byte Salt</summary>
- Public Function RegisterPassword(ByVal pw As String) As Byte()
- Dim salt(15) As Byte
- _Random.GetBytes(salt) 'generiert eine starke Zufallsfolge ins Salt-Array
- Dim bytes = ConcatArray(salt, Encoding.ASCII.GetBytes(pw)) 'verknüpft salt und Passwort
- Dim hsh = _Sha.ComputeHash(bytes) 'errechnet aus beiden den Hash
- _SaltedHash = ConcatArray(salt, hsh) 'stellt das salt voran
- Return _SaltedHash
- End Function
- Public Function VerifyPassword(ByVal pw As String, ByVal saltedHash As Byte()) As Boolean
- Dim saltHash = SplitArray(saltedHash, 16) 'trennt salt und hash
- Dim testBytes = ConcatArray(saltHash(0), Encoding.ASCII.GetBytes(pw)) 'verknüpft salt und Passwort
- Dim testHash = _Sha.ComputeHash(testBytes) 'errechnet aus beiden den Hash
- VerifyPassword = saltHash(1).EachEquals(testHash) 'test auf gleichheit jedes bytes
- Me._SaltedHash = If(VerifyPassword, saltedHash, Nothing)
- End Function
- Public Function CreateCryptoStream(ByVal strm As Stream, ByVal encrypt As Boolean) As CryptoStream
- If _SaltedHash.Null Then Throw New UnauthorizedAccessException( _
- "there is no password registered or verified")
- Dim transform = CreateTransform(encrypt)
- Dim mode = If(encrypt, CryptoStreamMode.Write, CryptoStreamMode.Read)
- Return New CryptoStream(strm, transform, mode)
- 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