Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
Server - Mehrere Klassen sollen Daten senden und empfangen
Also ich sitze derzeit immer noch an meinem Server-Programm, das später für einen Messenger genutzt werden soll. Nun habe ich folgendes Problem. Wenn der Client bspw. anfangs einen String sendet der "register" heißt, dann erstellt das Server Programm eine Instanz einer Kasse die sich darum kümmern soll weitere Informationen vom Client zu empfangen und den Client anhand der nachfolgend gesendeten Informationen in eine Datenbank einträgt und dem Client Rückmeldung erstattet. Sendet der Client zum Anfang den String "login" dann erstellt mein Server-Programm eine Klasse, die ebenfalls weitere Daten vom Client entgegennehmen soll, diese Daten dann mit den Daten in der Datenbank abgleicht und dem Client rückmeldung erstattet (Normale Registrierungs- und Login-Funktionen). Dann soll es noch weitere Klassen geben, die je nach empfangenen String erstellt werden und bestimmt Funktionalitäten bieten (Das ist sozusagen ne Mischung aus nem Command-Pattern und ner Factory-Klasse).
Viele diese Klassen haben aber den Umstand gemeinsam, dass sie sowohl Daten empfangen und senden können sollen.
Jetzt wollte ich mal fragen, ob es sinnvoll ist jeder dieser Klassen Attribute von bestimmten Klassen zu geben, die sich darum kümmern, oder ob es eine Bessere Möglichkeit gibt, um das zu realisieren? (Möglicherweise global auf eine Sender- bzw. Empfänger-Klasse zugreifen)
Oder ist möglicherweise auch der gesammte Ansatz fehlerhaft und sollte neu überdacht werden?
Sollte mein Text etwas zu unüberschauber sein und ihr noch Fragen dazu haben, dann Fragt einfach ^^
du arbeitest also nach dem für-jeden-client-einen-thread-prinzip ...
nun ... erstmal sollte dein grundlegender aufbau so aussehen
Server-Klasse
Connection-Klasse
in die server-klasse kommt nur der code um den server zu starten und in einem loop auf connections zu warten ... sowie entsprechende collections die die connections sammeln und auch inter-connection-kommunikation anbieten
in die connection-klasse ... die übrigens dann der thread ist ... kommt alles rein was für die kommunikation mit dem clienten zuständig ist
so ... und ob du alles weitere auf grund komplexität in mehrere klassen aufteilst oder nur in methoden auslagerst ... die vorgehensweise ist gleich : wenn du mit commandos arbeitest musst du diese in einem protokoll definieren ... und auch wie man die commandos und deren folge-sequenzen voneinander trennt
wenn du also alles was zur registrierung gehört in einzelnen nachrichten sendest ... dann verpass diesen ein einheitliches präfix und schiebe alles mit dem selben präfix an den selben handler
oder komplizierter : objekte hin und her schicken und mit einem message-flag den typ bestimmen und darauf hin die methode / instanz wählen die für die verarbeitung zuständig ist
einfach alles durcheinander schicken und dann aufm server wieder trennen wollen ... glaub mir ... bau dir einfach n kleines protokoll und fertig
[OT]wenn du dir mal die mühe machen würdest wüsstest du das ich auf interpunktion und korrekte typografie grundsätzlich verzichte ... orthografisch und grammatikalisch ist es dennoch korrekt ... was bei dir ja in dem einen post komplett daneben war
btw : WENN du darüber diskutieren willst dann machs bitte per PN ... oder pack es wenigstens in spoiler/ot blöcke[/OT]
Ich hatte erst überlegt, die Verbindungen über nen Thread-Pool anzunehmen allerdings, habe ich den Gedanken wieder verworfen, da diese Verbindungen erhalten bleiben sollen, da der Server die Clients ja über einkommende Nachrichten auf dem laufenden halten soll und wenn dann keine freien Threads mehr im Pool sind, könnte sich ja auch kein Client mehr zum Server Verbinden aber möglicherweise gibt es da ja einen Weg der weniger Speicher und Zeitaufwand benötigt, wüsstest du da was?
in die server-klasse kommt nur der code um den server zu starten und in einem loop auf connections zu warten ... sowie entsprechende collections die die connections sammeln
Auch auf die Gefahr hin, jetzt wieder als totaler Noob dazustehen aber... den Begriff "inter-connection-kommunikation" höre ich zum ersten mal. Hab mir mal grade was bei Wikipedia dazu durchgelesen, weiß aber leider nicht wie das in Bezug zu der Server-Anwendung steht.
in die connection-klasse ... die übrigens dann der thread ist ... kommt alles rein was für die kommunikation mit dem clienten zuständig ist
Das kommt so ungefähr hin. Meiner Connection-Klasse wird der Socket vom Client übergeben und dann werden 2 weitere Klassen (für das Empfangen und Senden der Daten) erstellt und denen wird der Input- bzw. OutputStream vom Client übergeben.
wenn du mit commandos arbeitest musst du diese in einem protokoll definieren ... und auch wie man die commandos und deren folge-sequenzen voneinander trennt
wenn du also alles was zur registrierung gehört in einzelnen nachrichten sendest ... dann verpass diesen ein einheitliches präfix und schiebe alles mit dem selben präfix an den selben handler
oder komplizierter : objekte hin und her schicken und mit einem message-flag den typ bestimmen und darauf hin die methode / instanz wählen die für die verarbeitung zuständig ist
einfach alles durcheinander schicken und dann aufm server wieder trennen wollen ... glaub mir ... bau dir einfach n kleines protokoll und fertig
Ja natürlich mache ich das über ein Protokoll desweiteren sende ich die Komandos auch nicht als String sondern als Integer, das habe ich nur deshalb nicht erwähnt, weil ich der Meinung war, das mein Anfänglicher Text sowieso schon unübersichtlich genug war. ^^
Ich habe mir den Aufbau des Protokolls ungefähr so vorgestellt:
Die 1. 4 Byte: Int-Wert des Komandos z.B. "1" für die Registrierung, "2" für Login, "3" für die Anfrage nach nem öffentl. Schlüssel... (Die Kommandos sind ja nur da, damit der Server weiß, wie er die nachvolgenden Daten zu behandeln hat)
Bei der Registrierung könnte das Protokoll dann so aussehen:
1. Byte = Account-Länge
2. Byte = Passwort-Länge
3. Byte = E-Mail-Länge
4. Byte = AES-Key-Länge
danach werden dann die AES-Verschlüsselten Infos (Account, Passwort und E-Mail) und der RSA-Verschlüsselte AES-Key hinten darn gehangen. Anhand der Anfangs angegebenen Längen der einzelnen Werte kann dann im Server auch ausgewertet werden, wieviel Bytes für das Empfangen der einzelnen Informationen zur verfügung gestellt werden muss.
Jatzt einfach mal die Frage, ob ich das so beibehalten kann oder ob ich da möglicherweise was überdenken sollte?
Desweiteren sollte noch dazu gesagt sein, dass ich das bis jetzt so lösen möchte:
Die 1. 4 Byte werden empfangen und der Int-Wert als Parameter an eine Factory-Klasse übergeben, die Dann je nach Komando ein Objekt erstellt das dafür zuständig ist, das jeweilige Protokoll zu Prüfen / zu Verarbeiten, und diesem Objekt werden dann die weiteren empfangenen Daten übergeben. Wäre das eine Angebrachte vorgehensweise?
hmmm ... ich bin dann doch noch etwas verwirrt und muss jetzt mal konkret nachfragen
sendest du ALLE daten die zu EINER anweisung gehören auch in einem block ... oder hast du zwischen-drin mehrere read-calls und damit getrennt blöcke die du korrekt zusammenfügen willst ?
für ersteres : ich würds nicht mal auf klassen ausdehnen ... vor allem wenn die objekte dann nur für die abarbeitung eines commandos genutzt werden und danach vermutlich eh im garbage landen ... das würde ich schon auf methoden runter brechen
sendest du ALLE daten die zu EINER anweisung gehören auch in einem block ... oder hast du zwischen-drin mehrere read-calls und damit getrennt blöcke die du korrekt zusammenfügen willst ?
Naja als bei der registrierung z.b. muss ich den server erst nach seinem öffentlichen schlüssel fragen, da die daten verschlüsselt übertragen werden sollen.
sendest du ALLE daten die zu EINER anweisung gehören auch in einem block ... ich würds nicht mal auf klassen ausdehnen ... vor allem wenn die objekte dann nur für die abarbeitung eines commandos genutzt werden und danach vermutlich eh im garbage landen ... das würde ich schon auf methoden runter brechen
oder hast du zwischen-drin mehrere read-calls und damit getrennt blöcke die du korrekt zusammenfügen willst ? > warum ?
die erklärung hast du schon gegeben
du sendest also an den server das commando : register
der server sendet dir einen schlüssel
du sendest die daten im anschluss an den server
und willst JETZT bei der zweiten sendung die daten dem register zu ordnen ... wie ich sagte > PROTOKOLL ! ... denn das nutzt du hier definitiv NICHT
beispiel wie dein jetziger code läuft
client > server : register
server > client : 123456 (key)
client > server : 654321 (verschlüsselte daten)
wie das ganze mit einem sauberen protokoll aussehen würde
client > server : register|getkey
server > client : register|key|123456
client > server : register|data|654321
und schon ist dein zuordnungsproblem gelöst ...
und wie bereits erwähnt : erzeuge nicht für jede nachricht ein neues objekt wenn es sich vermeiden lässt ...
Hmm... Das sieht natürlich um einiges besser zu verarbeiten aus, als das was ich vor hatte ^^
Dazu hätte ich aber noch (wie sollte es auch anders sein) einge kleine Fragen. =)
Wenn ich jetzt z.B. eine Nachricht verschicken möchte, dann könnte das Protokoll ja beispielsweise so aussehen:
client > server : message|receiverid|123456|content|irgend ein Text
(Natürlich verschlüsselt)
Jetzt besteht allerdings das Problem, dass wenn im Inhelt der Nachricht das Trennzeichen "|" vorkommt, es da wohl probleme mit dem Verarbeiten des Protokolls geben wird, jetzt könnte ich diese Nachricht zwar Base64-Encodiert in das Protokoll packen. Wenn ich allerdings noch Datei-Anhänge verschicken möchte, dann ist Base64 wohl ehr eine schlechte Lösung, also wie ralisiere ich das am besten?
Und noch eine kleine Frage...
wenn ich jetzt z.B. das Protokoll für die Registrierung verschlüsseln möchte, sollte ich dann Account, Passwort und Email einzeln verschlüsseln und dann in das Protokoll packen oder sollte ich das alles unverschlüsselt ins Protokoll packen und dann den gesamten Teil des Protokolls zusammen verschlüsseln?
Base64 ist doch schon mal ein guter ansatz ... warum soll es für dateien weniger gut geeignet sein als für normalen text ?
Base64 macht nicht mehr als 3 bytes nutz-daten in 4 bytes code-wörter zu übersetzen ... der input ist egal
zur verschlüsselung : anstatt nur die einzelnen daten zu verschlüsseln kannst du doch auch einfach den gesamten daten-austausch verschlüsseln ... also die streams direkt über cipher-streams laufen lassen ... oder die daten an sich komplett verschlüsseln und dann verschicken (mach ich aktuell so ... aber dein ansatz mit Base64 regt mich grad zum dran-rum basteln an)
Naja, wenn die Dateien doch mal etwas größer sind z.B. 30 MB, dann würden ja 40MB an informationen gesendet bzw. empfangen werden. Dadurch würde auch die Up- bzw. Download-Zeit steigen.
Da ich schon am überlegen bin auch eine mobile Android-Version des Messengers zu erstellen, kann das bei Smartphones schonmal etwas mehr Zeit ausmachen. Und dann ist das da ja auch immer so eine Sache mit der begrenzten Traffic-Flat bei Smartphones.
naja mein gott ... dann komprimiert man das ganze noch z.b. mit GZip*Stream oder einen der anderen die java anbietet ... müsste man halt mal ausprobieren welcher der streams aus einem Base64 am besten komprimieren kann ... wobei ich hier den buffer recht groß wählen würde für bessere entropieerkennung und damit stärke kompression
aber alles in allem dürfte man da schon was finden ... außerdem : wer sendet schon freiwillig über sein smartphone solche großen daten übers internet ? da ist selbst mit "großen" flats so um die 2-5 gb schnell feierabend
Hmm... ich denke mal, ich werde das über nen Ciphered Stream laufen lassen.
Allerdings habe ich damit noch nie gearbeitet, daher würde ich gern wissen, wie ich damit eine hybride Verschlüsselung zustande bekomme. Wenn ich das jetzt nämlich mal gedanklich überfliege, dann bräuchte ich ja eigentlich zum verschlüsselten senden der Daten 2 CipherOutputStreams einen mit AES-Cipher zum Senden der eigentlichen Daten und einen mit RSA-Cipher zum senden des AES-Keys oder geht das irgendwie eleganter?
was du grundsätzlich beachten solltest :
nutze immer nur einen stream ... es sei denn du weist genau was du tust und kümmerst dich um den krams drum rum selbst
hier bieten sich Filter*Streams an ... denn man kann sie beliebig in-ein-ander schachteln
spontan würde ich jetzt folgendes machen
vom Socket kommen erstmal grundsätzlich InputStream und OutputStream (sind zwar spezielle instanzen besonderer klassen ... aber betrachten wir sie einfach mal als das was sie laut API sein sollen)
InputStream und OutputStream sind die einfachsten streams und arbeiten ganz unten auf byte-ebene ...
letztenendes muss jede InputStream-implementierung die methode "abstract int read()" überschreiben ... weshalb auch letzten-endes alles auf diese eine methode runtergebrochen werden kann
das heißt : zwischen InputStream und OutputStream ... gleich ob jetzt über einen Socket oder nur über eine Pipe ... läuft die kommunikation im end-effekt so ab das man in den OutputStream bytes reinschaufelt (mit write(int)) und aus dem InputStream wieder rauszieht (mit read())
wie man diese byte-folgen zusammensetzt und interpretiert ist dann aufgabe der höheren FilterStreams
oft sieht man folgenden klassiker
new BufferedReader(new InputStreamReader(Socket.getInputStream))
derp ... hier schon der erste fehler
man baut um den InputStream einen Reader ... also einen Filter der die bytes die reinkommen grundsätzlich als druckbare zeichen ansieht und in char castet ...
dann packt man das ganze noch in einen Buffer ... um so z.b. die möglichkeit zu haben viele daten auf ein mal lesen zu können ... z.b. readLine()
damit sowas funktioniert muss der buffer also erstmal bis zu seinem trenner oder maximum daten aus dem drunterliegenden reader ziehen der wiederum seine daten von inputstream zieht
ergo : nur durch dieses konstrukt zieht man sehr viele daten aus dem InputStream raus die man dann an anderer stelle nicht mehr nutzen kann
deutlich besser ist es wenn man hier mit einem eigenen protokoll selbst filtert
dafür nutzt man z.b. DataInputStream und DataOutputStream
der vorteil ist relativ einfach erklärt
man behält die möglichkeit daten direkt RAW aus dem InputStream zu ziehen ... hat aber gleichzeitig auch die bequemlichkeit komplexere byte-ketten wie long oder double ... oder sogar ganze Strings zu versenden
ermöglicht wird dies weil die DataStreams mit recht viel overhead die daten in bytes zerlegen und dann direkt über die beiden grund-methoden write(int) und int read() arbeiten ... diese bleiben so also weiterhin verfügbar
gut ... wichtig ist natürlich das man sich auf ein protkoll festlegt und zu jedem was auf der einen seite gesendet wird die passende read-methode auf der anderen seite aufruft ... aber dazu gleich mehr
so, welchen vorteil bietet es jetzt also den OutputStream vom Socket erstmal in einen DataOutputStream zu packen ?
eigentlich nur einen : das man bequem bereits komplexere daten über einen Stream schicken kann ohne das dieser von Writern oder der gleichen zugemüllt oder auf der anderen seite leergezogen wird
das hilft jetzt aber beim Cipher-problem
man erzeugt auf der einen seite ein RSA-keypair und initialisiert sich damit einen de-crypt Cipher
den public-key lässt man durch Base64 laufen und erhält so einen String ... den man bequem über DataOutputStream.writeUTF8(String) senden kann
der vorteil an DataOutputStream.writeUTF8(String) und DataInputStream.readUTF8() ist das sich diese methoden selbst um die länge des strings kümmern
es wird also mit ein paar bytes overhead erstmal die länge des strings übermittelt bevor der string selbst gesendet wird
so weis die andere seite wie viele daten sie lesen muss um den kompletten string zu haben
auf der anderen seite lässt man den String wieder durch Base64 laufen und erhält so das roh-material um sich mit einer Factory wieder einen gültigen RSA-Public-Key zuerzeugen
damit initialisiert man sich jetzt einen crypt Cipher und verschlüsselt damit den vorher erstellten AES-key
dafür gibt es sogar einen speziellen modus : Cipher.WRAP_MODE ... sowie das gegenstück Cipher.UNWRAP_MODE und die dazu gehörigen methoden Cipher.wrap(Key) und Cipher.unwrap(byte[], String, int)
der vorteil liegt hier darin das man so die umwandlung von Key in byte[] und wieder zurück der Cipher-instanz überlassen kann ... spart letztenendes zwar nur ein paar zeilen und sieht halt lediglich eleganter aus ... aber man vermeidet auch fehler
so ... nach dem man nun also den AES-key mit RSA verschlüsselt hat erhält man ja wieder ein byte-array ... das man wieder durch Base64 laufen lässt und den String zurück schickt
auf der anderen seite läuft das nun umgekehrt ab : Base64 > Cipher > Key
und schon hat man auf beiden seiten den AES-key ... gesichert über RSA ... und hat bisher immer noch sauber über ein Stream-paar gearbeitet ... nämlich die DataStreams
so ... nach dem man nun den AES-key auf beiden seiten hat intialisiert man sich damit die nötigen Cipher ... und nimmt sich jetzt erst den CipherStream ... dem übergibt man ... und jetzt achtung ! : den DataStream ... also nicht noch mal neu Socket.get*Stream() sondern direkt den DataStream mit dem man bisher gearbeitet hat ... und arbeitet dann nur noch mit den CipherStreams
jetzt hat man aber wieder die möglichkeit verloren bequem daten über die streams zu schicken ... kein problem .. macht auch nix ... weil wir ja erstmal noch komprimierung wollten ...
dieser schritt ist jetzt austauschbar ... entweder man packt auf die DataStreams erst den GZipStream und dadrauf dann den CipherStream ... oder umgekehrt ... müsste man sich ausprobieren ...
so .. und nach dem man nun eine verschlüsselte und komprimierte verbindung hat kann man jetzt dort oben drauf entweder erneut neue DataStreams packen ... oder anderes wie Reader und Writer ...
das wäre jetzt die "elegante" variante wie ich sie bevorzugen würde ... wobei es wie gesagt einiges an testen bedarf wie rum das geschachtel nun am besten ist ... aber das dürfte man auch hinbekommen
eine wichtige anmerkung muss ich noch machen : da AES eine block-chiffre ist und auch eine komprimierung meist mit einer art von blöcken arbeitet ... bietet sich sowas schon eher nur für daten an wo man weis das der output der letztendlich übertragen wird auf jeden fall größer als die blöcke des Cipher und Compress sind ... sonst bleiben irgendwo daten hängen und werden nicht vollständig oder erst mit den nächsten daten übertragen
man kan hier zwar mal mit flush() spielen ... bedarf aber auch etwas an testen
ich selbst hab jetzt meinen code noch nicht umgebaut ... aber hab mich mit dem post hier gerade selbst zu ner neu-implementierung angestachelt ...
Absolute Klasse...
Das hast du wirklich super verständlich erklärt.
Könntest ja mal ein kleines Tutorial daraus machen ^^
OK, meine letzte Frage ist jetzt nur noch rein aus neugier...
Und zwar schreibst du ja unter anderem:
so ... nach dem man nun den AES-key auf beiden seiten hat intialisiert man sich damit die nötigen Cipher ... und nimmt sich jetzt erst den CipherStream ... dem übergibt man ... und jetzt achtung ! : den DataStream ... also nicht noch mal neu Socket.get*Stream() sondern direkt den DataStream mit dem man bisher gearbeitet hat ... und arbeitet dann nur noch mit den CipherStreams
nun ... in diesem speziellen fall macht es keinen unterschied ob man dem konstruktor des CipherStream den DataInputStream übergibt oder nur den InputStream vom Socket ... man muss dann nur darauf achten das man die referenz zum DataInputStream irgendwie sicher wegwirft damit man darüber nicht versehentlich gelesen wird ... andere seite : man muss für sorgen das nicht mehr in den DataOutputStream geschrieben wird wenn bereits der CipherOutputStream drüber gesetzt wurde
ich habe mit meinem text auf auf sowas abgezielt das man direkt schreibt
und das man halt dieses "Socket.getInputStream()" eben nur ein mal verwenden sollte ...
gut ... wie gesagt ... bei DataInput/OutputStream ist es wirklich nur ein sollte da diese streams wirklich sehr transparent sind und intern so gut wie keine buffer nutzen (ist bisschen ist schon nötig, aber es hält sich mit einem byte-array und einem char-array von jeweils der größe 80 in grenzen) ... und Socket.getInputStream() eh immer wieder ein und genau das selbe objekt liefert ...
... aber um halt trotzdem fehler zu vermeiden, gerade wenns um threads geht, sollte man halt die calls relativ gering halten und lieber noch eine eben weiter "stapeln"
denn es tut überhaupt nicht weh wenn unter dem Cipher/GZip konstrukt noch mal als zwischenschicht n DataStream liegt der dann die calls von read() und write() einfach nur direkt "durch schleift" und selbst nichts weiter mit den daten macht
zumindest ist es sicherer als wenn man dann irgendwo noch ne referenz auf den stream oder datastream hat obwohl schon über den cipher daten verarbeitet werden ... weil das führt zu nem kompletten abbruch und benötigt einen neuen handshake ... besser sowas umgeht man direkt dadurch das man die referenz an den datastream einfach als basis nimmt und nach zuweisung (also z.b. GZipInputStream(dataIn)) den zugriff auf "dataIn" selbst unterdrückt oder es einfach NULL setzt ... so das dann über den GZip auf den stream weiter zugegriffen werden kann .. man aber nicht mehr "zwischen drin" rankommt und dadurch die kommunikation nicht mehr stören kann
ist halt bei allem was mit header und blöcken arbeitet (also Cipher und GZip streams) sehr wichtig
auch hängt es mit dem buffer des OS zusammen wenn z.b. schon die daten von java raus sind ... aber noch im stack des OS hängen ... so würde man dann plötzlich einen ungültigen datenblock versenden
außerdem finde ich es persönlich einfach "sauberer" wenn man einen vorhandenen filter-stream mit dem nächsten umbaut und somit quasi eine kette bildet anstatt überall hier und da mal abzuzweigen und dann einen baum erzeugt ...
OK, dann werde ich mich mal weiter an die Arbeit machen.
Als erstes werde ich mir mal Gedanken darüber machen wie ich mein Protokoll am saubersten aufbaue und dann werde ich weiter sehen.
Ich danke dir noch mal recht herzlich für deine enorme Hilfe und für deine Geduld mit mir. =)
konnte dir in dem punkt nur so gut weiterhelfen weil ich mich auf diesem speziellen themengebiet nun mal sehr gut auskenne ...
alles was irgendwie mit net-code zu tun hat finde ich immer ne lösung ... ob nun am anfang recht rudimentär ums überhaupt ans laufen zu bekommen oder wenn ich mich selbst mit so ner ausführlichen anleitung anstachel wie hier bau ich auch gerne mal n "kunstwerk" draus
auch hab ich relativ viel erfahrung sammeln können und weis daher was für welchen einsatzzweck geeignet ist ... auch wenn ich mich noch nie mit RMI oder NIO auseinander gesetzt habe ... liegt auch daran das ich das ganze nur hobby-mäßig mache und wenn es mal ein projekt weiter als bis über die grenzen meines LAN schafft dann auch nur relativ kurzlebig für einen kleinen personen kreis ist ...
tja ... wunderschöne erklärung ... und die grund-idee sogar gar nicht mal so falsch ... aber für die praxis komplett untauglich ... jetzt weis ich auch wieder warum ich meine verschlüsselte verbindung damals so komplett anders implementiert habe
ich hab mal n bissl code zusammengeschustert ... aber funktionsfähigkeit ? NOPE
schlüssel-austausch hat ja soweit noch ganz prima funktioniert ... und die grund-verbindung wenn "nur" verschlüsselung genutzt wird haute auch soweit hin ... aber spätestens mit dem drumlegen des GZIP*Stream war feierabend
folgende probleme treten auf
also symetrisches verfahren habe ich AES/CBC/PKCS5PADDING genutzt ... was an sich ja auch wie gesagt super funktioniert ... nur leider taugt es nicht mal für einen "chat"
das problem wenn man mit Cipher*Stream arbeitet ist das AES natürlich erstmal die blöcke voll bekommen muss bevor die daten versendet werden ... auch wenn man explizit flush() callt ...
und selbst flush() hat nicht den gewünschten effekt als das es mal eben den block paddet und dann sendet ... nein ... es sendet nur die bereits verarbeiteten daten die nur noch nicht gesendet wurden (auch in der doc nachzulesen)
und die GZIP-komprimierung hab ich gar nicht ans laufen bekommen weil ich am ende doch nur exceptions bekommen habe
wobei GZIPOutputStream ja noch zusätzlich zu flush() die methode finish() anbietet ... auf die ich so aber keinen zugriff hatte da ich am ende ja nur wieder Data*Stream haben wollte ... also letztenendes alles gekapselt ... sah es dann so aus
zum glück hab ich n server mit dem ichs gleich mal testen konnte ... und alleine was mir wireshark an ausgehenden und eingehenden datenverkehr gezeigt hat haut da wohl so einiges überhaupt nicht hin ...
letzten endes habe ich es damals dann so umgesetzt
zwei klassen : RSAHelper und AESHelper
RSAHelper war nur für den austausch des RSA-public sowie des verschlüsselten AES zuständig
AESHelper hat dann entsprechende methoden angeboten direkt Strings zu versenden (HEX encoded > mal eben das doppelte an datenmenge) und intern eine crypt() methode hatte die ein byte-array durch den cipher geschickt und am ende doFinal() gecallt hat (code hatte ich auch nur von nem anderen post auf StackOverflow)
es lief dann also so ab :
daten als String aufbereiten
durch AESHelper jagen (String > byte-array > crypt() > verschlüsseltes byte-array > HEX-String)
und das ergebnis dann normal über einen PrintStream zur gegenstelle wo ein BufferedReader(InputStreamReader(SocketInputStream)) lief ...
ergo : meine beschreibung lässt sich so scheinbar nur auf datei-verschlüsselung anwenden ... und scheinbar auch nur wenn man direkt mit den nötigen instanzen der streams arbeitet ... warum allerdings die kompression völlig versagt ... ich werd aus der exception nicht schlau
vielleicht hat ja noch jemand anders eine implementierung die funktioniert ...
hier mal mein leider gescheiterter versuch
Das war anfangs auch mein Plan. Doch ich musste feststellen, dass man in Java so ohne weiteres keine Schlüssel > 128 bit nutzen kann. Jetzt bin ich grade dabei mich mit BouncyCastle zu beschäftigen. Aber die Dokumentation dazu ist grauenhaft. Deswegen bin ich grade dabei mich durch ein paar Tutorials und Anleitungen zu wühlen und zu gucken, wie genau das alles funktioniert. Möglicherweise hilft dir die Lib ja etwas weiter, hier mal ein Link dazu:
BouncyCastle ist mir durch aus bekannt ... und auch das man mit java erst die policy-files updaten muss bevor man AES256 nutzen kann (us export recht und so ...) ... aber ob bouncy bei den beschriebenen problemen weiterhilft glaube ich kaum ... auch wenn ich es noch nicht ausprobiert habe (wäre ja relativ einfach)
das problem liegt halt wie gesagt grundsätzlich darin das halt bei einer offenen verbindung immer nur die nächsten blöcke verarbeitet werden ... und GZip als Stream zur kompression von daten über eine socket-verbindung gänzlich versagt
man müsste die entsprechenden streams selbst implementieren um sicher zu stellen das bei einem input ins die methode auch ein output auf den stream erfolgt ... könnte man sich ja mal ransetzen ...
problem dürfte allerdings sein das ja eigentlich grundsätzlich byte-weise (warum hier ein int genommen wurde wenn doch nur die untersten 8bit genutzt werden ... wer weis) liest und schreibt ... und die methoden mit den arrays auf diese eigentlich zurückgreifen ... zumindest in den untersten ebenen
denn wenn man sich z.b. mal InputStream.read(byte[], int, int) ansieht wird hier nur in einem loop immer wieder read() aufgerufen bis halt der counter durch ist ... echt super implementierung
wirklich sinnvoll wäre es dann also nur von Filter*Stream zu erben und nur die nötigen methoden zu implementieren ... bei den anderen schmeißt man dann einfach ne IOException die so ja vorgegeben sind
werd mich da heute abend nach der spätschicht mal ran machen
Hmm...
Ich seh schon, da wird noch einiges an Arbeit auf mich zu kommen.
Ich werd mir jetzt die Tage mal Gedanken darum machen und evtl. mich an eine eigene Implementierung versuchen.
mir würde jetzt spontan nichts weiter einfallen als entsprechende klassen von Filter*Stream erbend noch mal neu zu implementieren ... da sie scheinbar für den einsatz wie man sie verwenden könnte ... und zugegeben : rein vom logischen aufbau hätte es ja so passen können ... nicht geeignet sind ... abhängig ob sie überhaupt für diese verwendung gedacht sind/waren
Naja, mein Problem lieg derzeit noch in der mangelnden Praxiserfahrung in diesem Bereich.
Da fällt mir grade noch ein, wo speicher ich denn am besten die öffentlichen Schlüssel der befreundeten Clients? Bis jetzt wollte ich das so machen, dass wenn ClientA eine Freundschaftsanfrage an ClientB sendet und Client B diese Anfrage bestätigt, diese ihre beiden öffentlichen Schlüssel austauschen, und dann in eine separate Datei speichern, der Name der Datei enthält dann als Präfix den Namen (oder die ID) des befreundeten Clients (damit der darin enthaltene Schlüssel später beim versenden einer Nachricht wieder zugeordnet werden kann). Wäre das eine angebrachte vorgehensweise oder sollte das evtl. anders gelöst werden? (z.B. die Schlüssel lieber in einer SQLite-DB speichern oder so)
aso ... du willst dann also nicht (nur) die verbindung zwischen client und server sondern auch zwischen zwei beliebigen clients (zusätzlich) verschlüsseln ...
klar ist es sinnvoll das dann jeder client so erstmal sein eigenes key-pair hat ... ich würde es dann aber so machen wie es auch in der praxis gemacht wird : beim verbindungsaufbau wird der public-key angefragt und mit diesem verschlüsselt dann der session-key übertragen
um die keys in ner art liste zu speichern müsste man erstmal sicher gehen das jeder "user" immer das gleiche RSA-pair hat ... also das der client beim ersten start ein key-pair erzeugt und dieses dann immer wieder nutzt ... wäre dann also eher so in die richtung zertifikat also halt random erzeugte schlüssel
konnte mich leider nicht an eine neu-implementierung machen da der abend etwas anders verlief als geplant ... und wochenende werd ich auch nicht zu kommen ... schade eigentlich
hab noch mal so bei grepcode über den source der crypto-klassen geguckt ... wie ich vermutet hatte : es wird immer nur update() genutzt ... es fehlt das doFinal() um einen block abzuschließen ... was man wie gesagt selbst umsetzen müsste ...
und beim GZIP*Stream hat man noch probleme mit headern und dann gibts da auch noch blöcke ... und das dann noch über n Cipher ... wenn man da nicht selbst irgendwas bastelt das halt beim input ... egal welche länge ... am ende fertige vollständige blöcke auf den stream geschrieben werden ... und dann muss man noch gucken was man so lesen muss um den block wieder vollständig zu haben
anbieten würde sich hier sowas in richtung wie beim Data*Stream vorher halt vorher die länge zu übertragen
und man darf nich vergessen : block-cipher und kompression macht nur sinn wenn man entsprechende datenblöcke überträgt ... nur für n einfachen client-to-client chat würde eher mehr overhead drauf gehen als dann überhaupt an nutzdaten übertragen wird
naja ... du siehst : da kann man viel dran basteln ... man müsste mal gucken wie es mit vorhandenen libs aussieht die halt verschlüsselung und kompression anbieten
aso ... du willst dann also nicht (nur) die verbindung zwischen client und server sondern auch zwischen zwei beliebigen clients (zusätzlich) verschlüsseln ...
Bin mir nich ganz sicher, wie du das meinst daher nochmal ne kleine (verkürzte) Erklärung, wie ich mir das vorgestellt hatte:
- ClientA verschlüsselt Nachricht (Hybride Verschlüsselung und allem Pi Pa Po...^^)
- ClientA sendet verschlüsselte Nachricht (+ Empfänger- und Absender-Account, usw.) an Server
- Server empfängt die Daten, prüft diese auf ihre Richtigkeit und guckt was damit zu machen ist
- Server erkennt, dass es sich um eine Nachricht handelt, und sendet diese an Empfänger (ClientB)
- ClientB entschlüsselt die Daten und stellt diese dar
um die keys in ner art liste zu speichern müsste man erstmal sicher gehen das jeder "user" immer das gleiche RSA-pair hat ... also das der client beim ersten start ein key-pair erzeugt und dieses dann immer wieder nutzt ... wäre dann also eher so in die richtung zertifikat also halt random erzeugte schlüssel
Ja also eigentlich hatte ich mir das auch so vorgestellt, dass der Client beim Erststart ein eigenes Schlüsselpaar erzeugt und abspeichert und dann ein einmaliger Schlüsselaustausch bei der bestätigung einer Freundschaftsanfrage stattfindet. Ich sollte evtl. noch dazu sagen, dass ich später noch vor habe die gesendeten Nachrichten mit einer digitalen Signatur zu versehen.
Dazu will ich allerdings doch nochmal was Fragen...
Wenn ich das so mache, kann ich dann für die digitale Signatur die gleichen Schlüssel nutzten wie für die Hybrid-Verschlüsselung oder sollten dafür 2 verschiedene Schlüsselpaare genutzt werden?
und beim GZIP*Stream hat man noch probleme mit headern und dann gibts da auch noch blöcke ... und das dann noch über n Cipher ... wenn man da nicht selbst irgendwas bastelt das halt beim input ... egal welche länge ... am ende fertige vollständige blöcke auf den stream geschrieben werden ... und dann muss man noch gucken was man so lesen muss um den block wieder vollständig zu haben
Das mit der GZIP-Cipher-Stream-Kombi habe ich auch schon abgeschrieben aber tiefergehende Gedanken kann ich mir dann darum machen, wenn ich das mit der Verschlüsselung anständig vorbereitet habe.
anbieten würde sich hier sowas in richtung wie beim Data*Stream vorher halt vorher die länge zu übertragen
Könnte man da nicht einfach dem zu sendenden Protkoll eine Arte Header verpassen, in dem alle nötigen Informationen enthalten sind?
und man darf nich vergessen : block-cipher und kompression macht nur sinn wenn man entsprechende datenblöcke überträgt ... nur für n einfachen client-to-client chat würde eher mehr overhead drauf gehen als dann überhaupt an nutzdaten übertragen wird