TCP Chat verschlüsseln

    • VB.NET

    Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von VaporiZed.

      TCP Chat verschlüsseln

      Moin,

      ich zeige euch nun eine simple Methode um den Netzwerkverkehr eurer Server<->Client-Anwendung zu verschlüsseln.
      Ich gehe dabei als Basis auf folgenden Thread ein:
      Multiserver (TCP)

      Wir fügen erstmal dem Server und dem Client-Projekt ein Modul namens "Crypt.vb" hinzu. Dieses brauchen beide Projekte.
      Der Code ist nicht von mir, es handelt sich hierbei um die SHA256-Verschlüsselung.

      Spoiler anzeigen

      VB.NET-Quellcode

      1. Imports System.Security.Cryptography
      2. Imports System.Text
      3. Imports System.IO
      4. Module Crypt
      5. Public Function Encrypt(ByVal plainText As String, ByVal Pass As String) As String
      6. Try
      7. Dim saltValue As String = "bitteAendern"
      8. Dim hashAlgorithm As String = "SHA256"
      9. Dim passwordIterations As Integer = 2
      10. Dim initVector As String = "@1B2c3D4e5F6g7H8"
      11. Dim keySize As Integer = 256
      12. Dim initVectorBytes As Byte() = Encoding.ASCII.GetBytes(initVector)
      13. Dim saltValueBytes As Byte() = Encoding.ASCII.GetBytes(saltValue)
      14. Dim plainTextBytes As Byte() = Encoding.UTF8.GetBytes(plainText)
      15. Dim password As New PasswordDeriveBytes(Pass, saltValueBytes, hashAlgorithm, passwordIterations)
      16. Dim keyBytes As Byte() = password.GetBytes(keySize \ 8)
      17. Dim symmetricKey As New RijndaelManaged()
      18. symmetricKey.Mode = CipherMode.CBC
      19. Dim encryptor As ICryptoTransform = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)
      20. Dim memoryStream As New MemoryStream()
      21. Dim cryptoStream As New CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)
      22. cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length)
      23. cryptoStream.FlushFinalBlock()
      24. Dim cipherTextBytes As Byte() = memoryStream.ToArray()
      25. memoryStream.Close()
      26. cryptoStream.Close()
      27. Dim cipherText As String = Convert.ToBase64String(cipherTextBytes)
      28. Return cipherText
      29. Catch
      30. End Try
      31. End Function
      32. Public Function Decrypt(ByVal cipherText As String, ByVal Pass As String) As String
      33. Try
      34. Dim saltValue As String = "bitteAendern"
      35. Dim hashAlgorithm As String = "SHA256"
      36. Dim passwordIterations As Integer = 2
      37. Dim initVector As String = "@1B2c3D4e5F6g7H8"
      38. Dim keySize As Integer = 256
      39. Dim initVectorBytes As Byte() = Encoding.ASCII.GetBytes(initVector)
      40. Dim saltValueBytes As Byte() = Encoding.ASCII.GetBytes(saltValue)
      41. Dim cipherTextBytes As Byte() = Convert.FromBase64String(cipherText)
      42. Dim password As New PasswordDeriveBytes(Pass, saltValueBytes, hashAlgorithm, passwordIterations)
      43. Dim keyBytes As Byte() = password.GetBytes(keySize \ 8)
      44. Dim symmetricKey As New RijndaelManaged()
      45. symmetricKey.Mode = CipherMode.CBC
      46. Dim decryptor As ICryptoTransform = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes)
      47. Dim memoryStream As New MemoryStream(cipherTextBytes)
      48. Dim cryptoStream As New CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)
      49. Dim plainTextBytes As Byte() = New Byte(cipherTextBytes.Length - 1) {}
      50. Dim decryptedByteCount As Integer
      51. Try
      52. decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
      53. Catch
      54. End Try
      55. memoryStream.Close()
      56. cryptoStream.Close()
      57. Dim plainText As String = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount)
      58. Return plainText
      59. Catch ex As Exception
      60. End Try
      61. End Function
      62. End Module



      Fangen wir mit dem Server an..

      Zu erst fügt ihr dem Struct "Connection" folgenden Wert hinzu:

      VB.NET-Quellcode

      1. Dim Key As String


      Das fertige Struct könnte nun so aussehen:

      VB.NET-Quellcode

      1. Private Structure Connection
      2. Dim stream As NetworkStream
      3. Dim streamw As StreamWriter
      4. Dim streamr As StreamReader
      5. Dim IP As String 'Ist nicht wichtig für dieses Vorhaben, aber nützlich für folgende Tutorials
      6. Dim Key As String
      7. End Structure


      Hier wird später dann der Key zum Ent- und Verschlüsseln für die jeweilige Connection gespeichert.

      So, der Server soll nun beim Eingehen einer Verbindung einen Key für den Client aussuchen, welchen nur der Server und der spezifische Client kennen.

      So, jetzt müssen wir als Server dem Client beim Joinen auch den Key mitteilen, dafür gehen wir in die Main-Methode und ersetzen

      VB.NET-Quellcode

      1. list.Add(c)


      hiermit:

      VB.NET-Quellcode

      1. Connection.IP = (IPAddress.Parse(CType(client.Client.RemoteEndPoint, IPEndPoint).Address.ToString())).ToString()
      2. Connection.Key = RandomString(10, 15)
      3. list.Add(Connection)
      4. Connection.streamw.WriteLine(Encrypt("/Key " & Connection.Key, "xored"))
      5. Connection.streamw.Flush()


      Ihr seht, ich habe im originalen Code

      VB.NET-Quellcode

      1. Dim c As New Connection


      durch:

      VB.NET-Quellcode

      1. Dim Connection As New Connection


      ersetzt.

      Was passiert hier?
      Sobald ein Client nun zum Server verbindet, wird dem Client ein Random Key zugewiesen, welcher zwischen 10 und 15 Zeichen lang ist.
      Anschließend wird in diesen einen spezifischen Networkstream der Key gesendet, damit der Client diesen auch hat.
      Andere Clients können diesen Key nicht bekommen. Als Standardpasswort für diesen einen Austausch ist hier "xored" gewählt, könnt ihr auch ändern.
      Nach diesem Step hier werden alle weiteren Austausche zwischen Server und Client mit einem Random-Key passieren.

      Die Funktion für den Random-Key ist folgende:

      VB.NET-Quellcode

      1. Function RandomString(minCharacters As Integer, maxCharacters As Integer)
      2. Dim s As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
      3. Static r As New Random
      4. Dim chactersInString As Integer = r.Next(minCharacters, maxCharacters)
      5. Dim sb As New StringBuilder
      6. For i As Integer = 1 To chactersInString
      7. Dim idx As Integer = r.Next(0, s.Length)
      8. sb.Append(s.Substring(idx, 1))
      9. Next
      10. Return sb.ToString()
      11. End Function


      Folgende Funktion muss im Original-Code geändert werden, damit ihr als Server auch versteht was der einzelne Client von euch möchte:

      VB.NET-Quellcode

      1. Private Sub ListenToConnection(ByVal con As Connection)


      Dort ersetzt ihr nämlich:

      VB.NET-Quellcode

      1. Dim tmp As String = con.streamr.ReadLine ' warten, bis etwas empfangen wird...


      mit:

      VB.NET-Quellcode

      1. Dim tmp As String = Decrypt(con.streamr.ReadLine, con.Key)


      In tmp steckt hier jetzt der Text, welchen der Client an den Server gesendet hat, z.B. eine Chat-Nachricht.


      Nun müsst ihr natürlich auch folgende Funktion im Original-Code anpassen:

      VB.NET-Quellcode

      1. Private Sub SendToAllClients(ByVal s As String)


      Hier wurde ja bisher unverschlüsselt an alle Clients der Text in den Stream geschrieben.

      Die neue Funktion lautet:

      VB.NET-Quellcode

      1. Private Sub SendToAllClients(ByVal s As String)
      2. For Each c As Connection In list
      3. Try
      4. c.streamw.WriteLine(Encrypt(s, c.Key))
      5. c.streamw.Flush()
      6. Catch
      7. End Try
      8. Next
      9. End Sub


      Ihr seht, dass hier jetzt an jede Connection das Paket gesendet wird und als Encryptionkey der Key aus dem Connection-Struct.

      Ihr könnt jetzt ganz regulär die Funktion aufrufen:

      VB.NET-Quellcode

      1. SendToAllClients("Hallo!")


      und jeder Client wird den Text erhalten, aber außenstehende TCP-Clients können diese Nachricht nicht entziffern.

      Gut, damit der Satz vor diesem hier aber auch stimmt, müssen wir natürlich auch im Client etwas verändern.


      Kommen wir zum Client..

      In der Funktion:

      VB.NET-Quellcode

      1. Private Sub AddItem(ByVal s As String)


      Müssen wir folgendes einfügen:

      VB.NET-Quellcode

      1. If (s.Contains("/Key")) Then
      2. Dim str() = {s}
      3. For Each sx In str
      4. Dim regx = New Regex(" +")
      5. Dim splitString = regx.Split(s)
      6. Dim ReceivedKey As String = (splitString(1))
      7. CryptKey = ReceivedKey
      8. Next
      9. End If


      Der Server sendet wie ihr gesehen habt beim Connecten den Befehl /Key XYZRANDOM an den jeweiligen Client.
      Hier lesen wir diesen Key aus und setzen ihn.

      Folgende Deklaration muss natürlich vorher gemacht werden:

      VB.NET-Quellcode

      1. Public CryptKey As String = "xored"


      Hier muss das Passwort genau so sein wie im Server. Wie auch im Server gilt dieses Passwort nur für diesen einen Key-Austausch.

      Das wars auch schon.

      Vorher habt ihr in den Chat so geschrieben:

      VB.NET-Quellcode

      1. streamw.WriteLine("Hallo")
      2. streamw.Flush()


      Das habe ich nun durch eine Funktion ersetzt:

      VB.NET-Quellcode

      1. Public Function SendPacket(ByVal Content As String)
      2. streamw.WriteLine(Encrypt(Content, CryptKey))
      3. streamw.Flush()
      4. End Function


      So sendet ihr jetzt an den Server:

      VB.NET-Quellcode

      1. SendPacket("Hallo!")


      Die Nachricht wird mit eurem persönlichen Verschlüsselungskey gesendet.

      Damit ihr jetzt als Client auch wisst, was der Server von euch will, ändert ihr noch folgende Funktion ab:

      VB.NET-Quellcode

      1. Private Sub Listen()


      Dort ersetzt ihr nämlich:

      VB.NET-Quellcode

      1. Me.Invoke(New DAddItem(AddressOf AddItem), streamr.ReadLine)


      mit:

      VB.NET-Quellcode

      1. Me.Invoke(New DAddItem(AddressOf AddItem), Decrypt(streamr.ReadLine, CryptKey))


      Der Ablaufplan ist nun wie folgt:
      1. Der Client connected zum Server
      2. Der Server denkt sich einen CryptKey aus und sendet dieses direkt in den Stream des Clients (Von außen nicht einsehbar)
      3. Der Client liest den Key aus und setzt diesen als globalen Key
      4. Der Client sendet nun Nachrichten an den Server verschlüsselt
      5. Der Server kann die Nachrichten entschlüsseln
      6. Der Server sendet Nachrichten an den Client auch verschlüsselt

      Wie ihr noch vertraulicher Pakete vom Server an den Client sendet, erkläre ich im nächsten Tutorial. Da wirds dann darum gehen
      einem spezifischen Client eine Nachricht zu senden, die gar nicht bei den fremden Clients ankommt, egal ob verschlüsselt oder nicht.

      Bei Interesse kann ich gerne mal das Projekt von kevin89 nehmen, umbauen und hochladen, falls die Anleitung zu abstrakt klang.

      Wenn ihr Fehler entdeckt, bitte melden. Ich habe es bisher nicht geschafft mit einem dritten TCP Client den Chatverlauf auszulesen.

      xored


      Meine Website:
      www.renebischof.de

      Meine erste App (Android):
      PartyPalooza
      Ich hab mich zwar nicht eingelesen, aber ich wage zu behaupten, dass das nicht sicher ist. Sobald man weiß, wie das Teil arbeitet (weil es hier ja steht), kann man sicher auch den Funkverkehr abzapfen und dann gleich mal den Schlüssel abfangen. Und das war's dann. Wenn ein Schlüssel auf gleichem Wege mmitgeteilt wird wie das verschlüsselte Zeugsl, ist m.E. immer aus die Maus. Aber ich bin da Laie, das können die Netzwerkexperten besser erklären oder sicherer machen oder begutachten oder wieauchimmer.
      Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

      Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
      Dassis schoma recht fragwürdig:

      VB.NET-Quellcode

      1. Public Function Encrypt(ByVal plainText As String, ByVal Pass As String) As String
      Moderne Verschlüsselungs-Algorithmen verschlüsseln keine Strings, sondern Byte-Folgen: Streams oder Byte-Array.
      Darauf aufbauend kann man alles verschlüsseln - ja, auch Strings.
      Nur sollte man einen Kern haben, der mit Byte-Folgen arbeitet, und das nicht von vornherein auf String reduzieren.
      Vielleicht will man ja auch mal ein Bild durch den Chat schicken - da wäre recht ineffizient, wenn man das erstmal in einen String umwandeln müsste und zurück.
      Mit WCF wäre das mit dem Verschlüsseln eventuell deutlich einfacher. Vielleicht erinnert sich @ErfinderDesRades ja ebenfalls daran.

      ich hab dazu 2016 oder so mal ein Tutorial hier erstellt

      VaporiZed schrieb:

      Sobald man weiß, wie das Teil arbeitet (weil es hier ja steht), kann man sicher auch den Funkverkehr abzapfen und dann gleich mal den Schlüssel abfangen.


      Das glaube ich tatsächlich nicht, weil der Schlüssel ja nicht an alle Verbindungen geht, sondern nur an den Client, welcher sich gerade verbindet.
      Aber insgesamt kann man natürlich nie 100% sicher sein.

      @ErfinderDesRades das stimmt, aber es geht hier ja in erster Linie bloß um den TCP Chat.


      Meine Website:
      www.renebischof.de

      Meine erste App (Android):
      PartyPalooza

      xored schrieb:

      Der Code ist nicht von mir
      Sollte es da nicht mindestens guter Stil (wenn nicht sogar rechtlich absolut unumgänglich) sein, eine Quelle zu benennen?


      Die Bedenken von @ErfinderDesRades teile ich nicht. Die gezeigte Anwendung der CryptoStream-Klasse ist doch genau was du beschreibst? String -> Stream/Byte-Folge -> Byte-Folge wird verschlüsselt


      weil der Schlüssel ja nicht an alle Verbindungen geht, sondern nur an den Client, welcher sich gerade verbindet.
      ... und damit automatisch an jeden, der die Komunikation mitliest.
      Für die anderen Clients ist der Schlüssel ohnehin irrelevant: entweder es handelt sich um eine Nachricht an alle, dann bekommen sie sie mit ihrem eigenen Schlüssel übermittelt; oder es handelt sich um eine private Nachricht zwischen zwei Clients, dann bekommen alle unbeteiligten Clients aber nichtsmals den verschlüsselten Inhalt, den die mit einem geklauten Schlüssel lesen könnten, daher ist für dieses Szenario "Schlüssel klauen" bedeutungslos.

      Hier liegt auf jeden Fall noch ein konzeptionelles Problem vor.

      fufu schrieb:

      Die Bedenken von @ErfinderDesRades teile ich nicht.
      Habich schlecht erklärt?
      Also der gezeigte Code, Encript(), Decript() kann zB. kein Bild crypten, weil können nur String.
      Intern wird der String in einen Stream gewandelt, ja - aber nach aussen hin kann das Ding nur String.



      Darüberhinaus ist eine Schwäche, dass Salt und InitVector hardcodet sind.
      Ich glaub, Usus ist, Salt und InitVector beim verschlüsseln zu generieren, und dem erzeugten CypherText unverschlüsselt voranzustellen (also mitliefern).
      @xored
      Ich muss @VaporiZed an dieser Stelle Recht geben. Bei TCP Verbindungen sind MITM-Angriffe ein großes Thema und da ist die Übertragung eines Keys grob fahrlässig. Für TCP-Verbindungen benutzt man asymmetrische Verschlüsselungen wie beispielsweise RSA. Der Public Key wird vom Empfänger an den Sender geschickt. Der Sender verschlüsselt die Nachricht mit dem Public Key und schickt die verschlüsselte Nachricht zum Empfänger. Der Empfänger entschlüsselt mit dem Private Key. Der Private Key bleibt zu jeder Zeit beim Empfänger und wird niemals!!! über das Netzwerk geteilt.


      Ein Computer wird das tun, was du programmierst - nicht das, was du willst.
      Moin, erstmal danke für deinen Beitrag :) sieh es als Chance, nochmal einiges zu lernen, ich möchte dir mal ein paar Tipps geben:
      • in Encrypt() und Decrypt() verschachtelst du die gesamte Methode in einem Try-Catch Block - so wird niemals ein Fehler auftreten. Das ist sehr gefährlich, weil wenn ein Fehler auftritt, passiert einfach nichts. @ErfinderDesRades hat da einen schönen Beitrag zu geschrieben: TryCatch ist ein heißes Eisen (Tipp: Entferne einfach das Try-Catch. Falls die Ver- oder Entschlüsselung fehlschlägt, möchte ich das auch wissen und dementprechend reagieren, z. B. dem Nutzer einen Fehler anzeigen. Das geht so nicht)
      • Verwende immer Using Blöcke, wenn es geht (docs.microsoft.com/en-us/dotne…tatements/using-statement). Was ist, wenn in Zeile 25. ein Fehler auftritt? Dann werden der Memory- und der Krptostream nicht geschlossen. Das ist ein riesen Problem, du musst sicherstellen, dass ggf. unmanaged Resources in deinem Code freigegeben werden, nachdem sie nichtmehr verwendet werden. Also, RijndaelManaged, MemoryStream, CrpyotStream unbedingt in einen Using Block!
      • die Random-Klasse ist nicht sicher für Kryptographie! Das heißt, dass man als außenstehender ggf. die Zahlen erraten kann, die du da erzeugst, was ein riesen Problem ist, weil es nur durch einen geheimen Key ja sicher ist. Hier lieber docs.microsoft.com/en-us/dotne…bergenerator?view=net-6.0 verwenden
      • Wie die anderen schon gesagt haben, bringt die Verschlüsselung gar nichts, weil du das Passwort übertragen musst und dafür einen festen Key brauchst, den auch der Angreifer kennt. Kryptographie ist wahnsinnig spannend, eben weil durch Mathematik irgendwie versucht wird, dies zu umgehen. Wie die anderen schon angemerkt haben, wird Public-Private Verschlüsselung wie RSA häufig verwendet, um den symmetrischen Key auszutauschen (also den Key, den du am anfang überträgst). Außerdem gibt es noch den Diffie-Hellman-Schlüsselaustausch. Lange Rede kurzer Sinn: Kryptographie ist extrem kompliziert und man muss genau wissen, was man tut. Genau deshalb gibt es den SslStream, der das alles für dich erledigt. Das bedeutet: Viel weniger Code für dich und einigermaßen sicher, weil du weniger falsch machen kannst.

      Btw was ist eine SHA256-Verschlüsselung? Normalerweise nennt man bei der Verschlüsselung den Algorithmus, der für die Verschlüsselung zuständig ist (und nicht den für's hashen, was SHA256 wäre), was in deinem Beispiel Rijndael ist bzw. AES.
      Mfg
      Vincent

      Kleine Formulierungskorrektur zu

      VincentTB schrieb:

      verschachtelst du die gesamte Methode in einem Try-Catch Block - so wird niemals ein Fehler auftreten.
      Es können schon Fehler auftreten. Man bekommt es so nur nicht mit.
      Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

      Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.