Verschlüsseln und Autentifizieren

    • VB.NET

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

      Verschlüsseln und Autentifizieren

      In diesem Tut gehts garnet um Kryptographie selbst, sondern um ein paar Grundkonzepte, was das Framework dafür bereitstellt, und wie mans verwendet.
      Insgesamt kann man sagen, die Kryptographie hat einen sehr hohen Stand der Technik erreicht, sowohl was das Knacken angeht als auch das Verschlüsseln. Der Stand der Verschlüsselungs-Technik ist im Framework enthalten, und zum Stand der Technik gehört, dass dann nicht geknackt werden kann, wenn man den Stand der Technik richtig anwendet :)
      Aber man muß sich einiges klar machen, sonst öffnet man Angreifern Tür und Tor. Ich selbst bilde mir auch erst seit 2 Tagen ein, die Konzepte hinreichend verstanden zu haben, nämlich erst nach der Auseinandersetzung mit diesem Thread, genaugenommen dieser Post - vielen Dank an picoflop!

      ,Sicher habich was übersehen, aber dieser Post ist doch tatsächlich die einzige Stelle im Internet, wo mal erklärt wird, was ein Salt ist, und was ein IV ist, und was der Unterschied, und wozu gut.
      Ich fund haufenweise zu Kryptografie, aber immer fehlte das eine oder annere Detail, dasses sich zu einem sinnvollem Ganzen zusammengefügt hätte.
      Aber dazu später.
      Zunächst mal klarmachen viel grundlegenderer Grundlagen:

      Kryptographie hat nix mit Strings oder Texten zu tun.
      Durcheinandergewürfelt werden rohe Bytes, und zwar in der grundlegendsten Form, in der Bytes auftreten können, nämlich als Stream.
      Die Bytes können Text bedeuten - aber auch alles andere. Was die Bytes bedeuten, geht die Kryptographie jedenfalls schon nix mehr an.
      Also Codesamples, die Ver-/Ent-schlüsseln anhand von Textboxen demonstrieren, sind schonmal verdächtig. Denn ein CipherText ist reiner Byte-Salat, und so garnet in einer Textbox darstellbar. Wenns gut gemacht ist, wird der Byte-Salat zur Anzeige nochmal umgeformt in einen Base64-String, aber das verschleiert immer noch die Mächtigkeit des Konzepts, nämlich dass jede Art von Daten verarbeitbar ist - weit mehr als nur Strings.
      Man redet zwar von Klartext und Cyphertext, aber damit sind keine Texte gemeint, sondern amorphe Byte-Folgen. Der einzige Text, den Kryptografie kennt, ist das Passwort, und manchmal ist auch dieses nicht-textuell.

      Authentifikation versus Verschlüsselung
      Es ist fundamental wichtig, Authentifikation und Verschlüsselung klar auseinanderzuhalten. Für beides braucht man ein Passwort, aber was vor sich geht ist total unterschiedlich (und wird total oft durcheinandergebracht).
      Bei beidem wird aus dem Passwort ein Schlüssel generiert ("abgeleitet"). Bei Authentifikation hieß es früher häufig, ein Hash werde gebildet, aber das ist heutzutage nicht mehr Stand der Technik.
      Das Ableiten des Schlüssels ist unumkehrbar, also aus einem Passwort kann man einen Schlüssel ableiten, aber es gibt kein Verfahren, um aus einem Schlüssel das Passwort rückzuschließen (jedenfalls nicht, wenn mans richtig gemacht hat).
      Beim Autentifizieren wird eiglich nichts mit dem Schlüssel angefangen, er wird nur gespeichert. Das Passwort wird nicht gespeichert - nirgends!
      Um sich zu autentifizieren gibt man ein Passwort ein, und darauf wird mit identischem Verfahren ein Schlüssel abgeleitet. Stimmt der gespeicherter Schlüssel mit dem frisch abgeleiteten überein, so ist man autentifiziert.
      Verschlüsseln
      Beim Verschlüsseln wird ebenso ein Schlüssel aus dem Passwort abgeleitet - mit dem dann aber ver-/ent-schlüsselt wird.

      Noch einmal die Begriffe:
      Das Passwort ist nicht der Schlüssel, sondern der Schlüssel wird erst davon abgeleitet.
      Dieses Ableiten ist keine Verschlüsselung, sondern ist eben das Ableiten eines Schlüssels - es gibt keinen anderen korrekten Begriff dafür. Es ist schon deshalb kein verschlüsseln, weil ein Schlüssel schon vom Wort her etwas ist, mit dem man etwas auf- und zu-schließen kann. Kann man nur "zuschließen", dann ists halt kein Schlüssel (sondern ein Nagel, oder ein Schweißgerät ;)).
      Es ist auch kein Verhashen, denn der angewandte Vorgang ist nach Stand der Technik komplizierter.

      Schlüssel-Ableitung: das Salt
      Das Salt ist eine der Gemeinheiten, die die Kryptographen sich ausgedacht haben, um den Rückschluß vom Schlüssel zum Passwort zu verunmöglichen.
      Die Idee ist, einen beliebig generierten zusätzlichen Schlüssel zu erstellen, und diese Byte-Folge in die Ableitung des Schlüssels aus dem Passwort mit einzuarbeiten.
      So wird erreicht, dass aus demselben Passwort, mit demselben Algorithmus, unterschiedliche Schlüssel abgeleitet werden!
      Ähm - wie soll man sich dann autentifizieren, wenn beim Ableiten jedesmal ein annerer Schlüssel rauskommt 8|. :?:
      Naja das Salt muß man halt mitliefern.
      Klingt bisserl bescheuert: Erst ein Salt generieren, dann überaus kunstvoll einen Schlüssel ableiten, in dem Passwort und Salt hineinverarbeitet werden, und dann das Salt doch wieder öffentlich mitliefern?
      Ja, genau. Das Salt ist eben kein zusätzliches Passwort, sondern ein Hilfsmittel, um zu bewerkstelligen, dass keine 2 Schlüssel sich gleichen, nichtmal vom selben Passwort abgeleitet :P.
      Es gibt nämlich Angriffe auf Passworte, die sich zunutze machen würden, wenn sich aus einem Passwort zweimal der gleiche Schlüssel ableitete.

      Verschlüsselung: der IV
      IV ist Abkürzung für "Initialization-Vector". Und ist ein ebenso dummes Byte-Array, wie zuvor schon Schlüssel und Salt.
      Der IV ist eine Notwendigkeit beim Verschlüsseln (wie schön, dass wir das jetzt eindeutig von Autentifizierung und Schlüssel-Ableitung abgrenzen können! ;)).
      Stand der Technik sind nämlich symmetrische Verschlüsselungen vom Typ "BlockChiffre mit Feedback". Dabei werden die Daten aufgeteilt und blockweise ver-/ent-schlüsselt. Die Verschlüsselung der einzelnen Blöcke wird aber variiert, nämlich aus dem Ergebnis des Vorgänger-Blocks wird ein "Zusatzschlüssel" abgeleitet, der in die Verarbeitung des nächsten Blocks mit einfliest (das "Feedback"). Sodass zwei aufeinanderfolgende identische Blöcke trotzdem unterschiedliche CipherTexte ergeben.
      Naja, und weil der erste Block keinen Vorgänger hat, muß man ihm quasi ersatzweise den Initialisation-Vector (IV) vorgeben.
      Also ungefähr die gleiche Idee wie mit dem Salt beim Ableiten des Schlüssels. Und auch dieselbe scheinbare Paradoxie: Erst wird der IV hochsicherheitsmäßig zufalls-generiert, und dann musser in aller Öffentlichkeit mitgeliefert werden, damit erfolgreich entschlüsselt werden kann.

      Es wieder einfach machen
      Das mit dem Salt und dem IV ist schon sehr umständlich und unintuitiv. Für eine Autentifikation muß man dann ja nicht nur Username und Schlüssel speichern, sondern zusätzlich noch dieses ominöse Salt.
      Und einem verschlüsselten Text müsste immer sowohl Salt als auch IV beigelegt sein, sonst kanner auch mit richtigem Passwort nicht gelesen werden.
      Die hier vorgestellte Lösung (Download hier, aber Erläuterung im nächsten Post) ist einfach und glaub nichtmal unüblich: Die öffentlichen Zusatz-Daten werden den eigentlichen Cipher-Daten einfach unverschlüsselt vorangestellt - sind ja doch nur Byte-Folgen.
      Also dem für die Autentifizierung abgeleiteten Schlüssel wird sein Salt vorangestellt, und dem CipherText werden sowohl IV als auch Salt vorangestellt.
      Und schon lassen sich wieder einfach zu benutzende Methoden coden, die ein Passwort gegen einen Schlüssel abgleichen (Autentifikation) bzw. die mit einem Passwort einen Ciphertext entschlüsseln - ohne dass der hier besprochene Zusatzkram irgendwo in der Benutzung auftauchen würde :D

      verblüffend, elegant, aber leider zu schwach
      @RodFromGermany' stellt einen sehr eleganten Algo vor, mit dem man einen String in Byte-Salat verwandeln kann und zurück.
      Aber - wichtig!! - als ernstgemeinte Verschlüsselung leider unbrauchbar: Also Dilletanten kann man damit abwehren, aber bereits ein Kryptographie-Amateur wird dem auf die Schliche kommen.

      Dateien
      • CrypterTester.zip

        (75,16 kB, 339 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 9 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Die Crypter-Klasse
      Ich habe beide Belange - Authentifikation und Ver-/Ent-schlüsselung - hineingepackt, und Merk-Variablen für Username und Password.
      Crypter kann neue User registrieren - dabei merkt er sich das Passwort, und gibt einen daraus abgeleiteten Schlüssel zurück, mit vorangestelltem Salt.
      Natürlich kanner ein Passwort auch verifizieren, anhand eines solchen saltedKeys.
      Für den Belang "Verschlüsselung" erzeugt er einen CryptoStream, der auf einem anzugebenden Stream aufsitzt.
      Wird verschlüsselt, so ists ein verschlüsselnder CryptoStream, und der zugrundeliegende Stream ist ein Ausgabestream (der etwa in eine Datei ausgibt). In den Cryptostream schreibt man dann unverschlüsselt hinein (etwa durch Aufsatz eines StreamWriters), und verschlüsselt ist.
      Wird entschlüsselt, so ists ein entschlüsselnder Cryptostream, und der zugrundeliegende Stream liest zB. aus einer Datei. Aus diesem Cryptostream kann man nun entschlüsseltes auslesen, etwa mit einem StreamReader.
      In der Sample-App wird übrigens weder StreamReader noch -Writer auf den Cryptostream aufgesetzt, sondern ein Dataset liest/schreibt seine Daten direkt hinein/heraus.

      VB.NET-Quellcode

      1. Imports System.Text
      2. Imports System.IO
      3. Imports System.Collections.Generic
      4. Namespace System.Security.Cryptography
      5. Public Class Crypter
      6. Public Username, Password As String
      7. ''' <summary>Der Return-Wert - ein abgeleiteter Key mitvorangestellten 16 Byte Salt - muß gespeichert werden,
      8. ''' da für zukünftige Passwort-Verifizierungen erforderlich</summary>
      9. Public Function RegisterPassword(ByVal pw As String) As Byte()
      10. Password = pw
      11. With New Rfc2898DeriveBytes(pw, 16, 1000)
      12. Return .Salt.Append(.GetBytes(32))
      13. End With
      14. End Function
      15. Public Function VerifyPassword(ByVal pw As String, ByVal saltedKey As Byte()) As Boolean
      16. Password = pw
      17. With New Rfc2898DeriveBytes(pw, 16, 1000)
      18. Dim sltkey = saltedKey.Split(16)
      19. .Salt = sltkey(0)
      20. Return .GetBytes(32).EachEquals(sltkey(1))
      21. End With
      22. End Function
      23. Public Function CreateCryptoStream(ByVal pw As String, ByVal strm As Stream, ByVal encrypt As Boolean) As CryptoStream
      24. Password = pw
      25. Dim rfc = New Rfc2898DeriveBytes(pw, 16, 1000) 'schlüsselableitfunktion, die 16 bytes salt generiert, und 1000 Iterationen ausführt
      26. Using AesProvider As New AesManaged
      27. With AesProvider
      28. If encrypt Then
      29. .Key = rfc.GetBytes(32) '32 bytes key ableiten
      30. strm.Write(.IV, 0, .IV.Length) 'vom AesProvider generierten IV unverschlüsselt reinschreiben
      31. strm.Write(rfc.Salt, 0, 16) 'rfc.Salt unverschlüsselt reinschreiben
      32. Return New CryptoStream(strm, .CreateEncryptor(), CryptoStreamMode.Write)
      33. Else
      34. .IV = ByteFromStream(strm, .IV.Length)
      35. rfc.Salt = ByteFromStream(strm, 16)
      36. .Key = rfc.GetBytes(32)
      37. Return New CryptoStream(strm, .CreateDecryptor(), CryptoStreamMode.Read)
      38. End If
      39. End With
      40. End Using
      41. End Function
      42. Public Function CreateCryptoStream(ByVal strm As Stream, ByVal encrypt As Boolean) As CryptoStream
      43. If Password.Null Then Throw New UnauthorizedAccessException("no password set")
      44. Return CreateCryptoStream(Password, strm, encrypt)
      45. End Function
      46. Private Function ByteFromStream(ByVal strm As Stream, ByVal n As Integer) As Byte()
      47. ReDim ByteFromStream(n - 1)
      48. strm.Read(ByteFromStream, 0, n)
      49. End Function
      50. End Class
      51. End Namespace
      Wie man sieht, gebe ich mir nichtmal Mühe, besonders geistreiche Salts oder IVs zu generieren, sondern belasse die einfach so, wie ich sie in den vorgesehenen Klassen vorfinde.
      Also beim Verschlüsseln setze ich den IV des AesProviders garnet, sondern lese ihn nur aus, um ihn unverschlüsselt in den Basis-Streams des CryptoStreams zu schreiben.
      Auch ein Salt arbeite ich nicht selbst ein, sondern ich nehme einfach dieses komische Rfc2898DeriveBytes - Dinges her, und teile ihm das Passwort mit sowie die gewünschte Länge des zu generierenden Salts.
      Dann leitet das Dings mir einen Schlüssel ab, und ich kann auch den dabei verwendeten Salt abrufen, und unverschlüsselt in die Ausgabe geben.
      Naja - Entschlüsselung/Autentifizierung dito, nur umgekehrt ;)

      Die Sample-App
      sind in Wirklichkeit 2 Apps: Die eine nimmt nur ein Passwort entgegen, und ver-/ent-schlüsselt ein Dokument. Natürlich muß das Passwort auch geändert werden können

      VB.NET-Quellcode

      1. Imports System.IO
      2. Imports System.Security.Cryptography
      3. Public Class Form1
      4. Private _DataFile As New FileInfo("..\..\DataDts.crypt")
      5. Private _Crypter As New Crypter
      6. Private Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
      7. _Crypter.Password = InputBox("", "Passwort eingeben", "adminPw")
      8. If _Crypter.Password = "" Then Me.Close() : Return ' -> Abbruch
      9. If Not _DataFile.Exists Then Return ' -> leeres Dokument öffnen
      10. Try
      11. Using strm = _DataFile.Open(FileMode.Open), crs = _Crypter.CreateCryptoStream(strm, encrypt:=False)
      12. Me.DataDts.ReadXml(crs)
      13. DataDts.AcceptChanges()
      14. End Using
      15. Catch ex As Exception
      16. MessageBox.Show("something failed", ex.GetType.Name)
      17. Me.Close()
      18. End Try
      19. End Sub
      20. Private Sub Form_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
      21. Me.Validate() 'ggfs. offene Editierungen zum Abschluß bringen
      22. If Not DataDts.HasChanges OrElse e.Cancel Then Return
      23. Select Case MessageBox.Show( _
      24. Me, "Änderungen speichern?", "Das Ende ist nahe", MessageBoxButtons.YesNoCancel)
      25. Case Windows.Forms.DialogResult.Yes
      26. Using strm = _DataFile.Open(FileMode.Create), crs = _Crypter.CreateCryptoStream(strm, encrypt:=True)
      27. DataDts.WriteXml(crs)
      28. End Using
      29. Case Windows.Forms.DialogResult.Cancel
      30. e.Cancel = True
      31. End Select
      32. End Sub
      33. Private Sub btChangePassword_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btChangePassword.Click
      34. Dim pw = InputBox("", "Passwort eingeben", "adminPw")
      35. If pw = "" Then Return
      36. _Crypter.Password = pw
      37. End Sub
      38. End Class


      Die annere App ist eine Art Datenbank: also es gibt eine Tabelle mit Usern und Schlüsseln, die zur Autentifizierung verwendet werden.
      Bei dieser App kann man sich registrieren, einloggen, und seine Registrier-Daten ändern.

      Usernamen
      Solange nur ein Dokument zu verschlüsseln ist, sind Username und Autentifizierung entbehrlich, denn entweder geht das Passwort oder nicht.
      Aber bei mehreren Datenpaketen, die unterschiedlichen Usern "gehören", muß man zusätzlich zum Passwort auch noch den Usernamen einführen. Für die eigentliche Passwort-Überprüfung ist der Username garnet erforderlich, aber es könnte vorkommen, dass 2 User sich mit demselben Passwort registrieren wollen. Den zweiten müsste man dann ablehnen - und damit hätte man ihm mitgeteilt, dass er auf die LogIn-Daten eines bereits bestehenden Users gestoßen ist :wacko:
      Aber beim LogIn mit Username kann man bereits ablehnen, wenn 2 User nur denselben Usernamen nutzen wollen - und damit ist noch nicht zuviel verraten, denn das Passwort wurde ja garnet verhandelt :D
      Sicherer wäre natürlich, man ließe garnet zu, dass User ihre Passworte selbst wählen, sondern man würde ihnen einfach welche zuteilen, und zwar starke Passworte.
      Dann hätte man ausnahmslos starke Passworte, bräuchte keine Usernamen zu verwalten, und die User müssten sich nur ihr zugeteiltes Passwort merken, nicht auch noch den Usernamen (so gings mir jdfs. gelegentlich: Passwort gemerkt - Username vergessen).

      CryptograpicException beim Entschlüsseln
      "Zeichenabstände sind ungültig und können nicht entfernt werden." (das verstehe wer will). Jedenfalls scheint es so, dass ein falsches Passwort schon auffliegt, bevor der entschlüsselte Text keinen Sinn ergibt, nämlich weil der Algorithmus gar nicht ausführbar ist.

      Die Datenbank-App funzt übrigens bischen doof, also doppelt gemoppelt:
      Nachdem man sich als User autentifiziert hat, werden die verschlüsselten User-Daten entschlüsselt und angezeigt.
      Da hätte man sich die Autentifizierung im obigen Sinne auch sparen können und gleich die Daten entschlüsseln - und den User halt ablehnen, wenn die Decryption failt.

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Das würde ich so nicht behaupten:

      ErfinderDesRades schrieb:

      Bei Authentifikation hieß es früher häufig, ein Hash werde gebildet, aber das ist heutzutage nicht mehr Stand der Technik.


      denn:

      ErfinderDesRades schrieb:

      Das Ableiten des Schlüssels ist unumkehrbar, also aus einem Passwort kann man einen Schlüssel ableiten, aber es gibt kein Verfahren, um aus einem Schlüssel das Passwort rückzuschließen (jedenfalls nicht, wenn mans richtig gemacht hat).
      Beim Autentifizieren wird eiglich nichts mit dem Schlüssel angefangen, er wird nur gespeichert. Das eigentliche Passwort hingegen wird nicht gespeichert - nirgends!
      Um sich zu autentifizieren gibt man das Passwort ein, und darauf wird mit identischem Verfahren ein Schlüssel abgeleitet. Stimmt der gespeicherter Schlüssel mit dem frisch abgeleiteten überein, so ist man autentifiziert.


      ist ein Hash.

      Hashes sind Einwegfunktionen und werden immer noch benutzt.
      SWYgeW91IGNhbiByZWFkIHRoaXMsIHlvdSdyZSBhIGdlZWsgOkQ=

      Weil einfach, einfach zu einfach ist! :D
      Jo, hast recht: Auch das Ableiten eines Schlüssels aus Passwort+Salt stellt eine HashFunktion dar.

      Edit: nee, doch nicht. Definition Wikipedia:
      "Eine Hashfunktion oder Streuwertfunktion ist eine Abbildung, die zu jeder Eingabe aus einer oft sehr großen Quellmenge eine Ausgabe aus einer kleineren Zielmenge erzeugt, den sogenannten Hashcode (oder Hashwert)."
      Das Ableiten eines Schlüssels kann also auch im weitesten Sinne nicht mehr als HashFunktion aufgefasst werden, denn der mathematische Begriff Abbildung wird überschritten dadurch, dass ein zufälliges Salt hinzu-generiert wird.
      Eben genau der Witz an der Sache mittm Salt: nämlich dass dasselbe Passwort zwei unterschiedliche Ausgaben erzeugt - ist der Grund, dass man hier nicht mehr von einer Abbildung sprechen kann, und also auch nicht mehr von einer Hash-Funktion :P

      Also die Ableitung eines Schlüssels aus bestehendem Passworts und bestehendem Salts (bei der Authentifikation) kann noch als HashFunktion gelten.
      Aber vorher das Generieren dieses Schlüssels unter Hinzuziehung des zufälligen Salts - dassis keine HashFunktion mehr, weil keine Abbildung, weil uneindeutig.

      Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von „ErfinderDesRades“ ()