Textverschlüsselung mit XOR

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

Hallo zusammen,

ich habe ein kleines Skript geschreiben was mit einen Text verschlüsseln soll. Das Verschlüsseln klappt auch schon soweit ich das beurteilen kann. Nun habe ich aber zwei Probleme.

1. Problem ist die Ausgabe der Bytes, das Terminalfenster wandelt diese teilweise automatisch in druckbare Zeichen um, gibt es einen Weg das zu verhindern? Eventuell selber die Zeichen wieder umwandeln und dann später zurückwandeln?
2. Problem ist das entschlüsseln. Ich weiß nicht wie ich es hinbekomme, dass wenn ich über einen input die Bytes eingebe, diese auch so wie ich sie eingebe wieder zur weiterverarbeitung im Programm nutzen kann.

Sonstige Verbesserungsvorschläge sind auch gerne gesehen.

Code: Alles auswählen

import random
import string


CHARSET = list(string.ascii_lowercase) + list(string.ascii_uppercase) + list(string.digits) + list(string.punctuation)
CHAR_TO_REPLACECHARS = {ord('ä'):'ae', ord('ö'):'oe', ord('u'):'ue', ord('ß'):'ss'}

def generate_random_key(lenght):
    return ''.join(random.choice(CHARSET) for _ in range(lenght))

def encrypt_text(message):
    key = generate_random_key(len(message))
    encrypt_message = bytes([byte ^ key.encode()[i] for i, byte in enumerate(message.encode())])
    return encrypt_message, key

def decrypt_text(encrypted_text, key):
    return bytes([byte ^ key.encode()[i] for i, byte in enumerate(encrypted_text)]).decode('ascii')

def main():
    message = input("Nachricht: ").translate(CHAR_TO_REPLACECHARS)
    encrypt_message, key = encrypt_text(message)
    print(f"Message:\n{encrypt_message}\n") #die Message würde ich gerne herauskopieren können und dann...
    print(f"Key:\n{key}\n")
    #...hier wieder über einen Input eingeben können (mit dem Key), sodass die Nachricht entschlüsselt wird
    decrypt_message = decrypt_text(encrypt_message, key)
    print(f"Decrypted message:\n{decrypt_message}")


if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

lenght schreibt man length.
Es ist komisch, dass Du als Input nur einen beschränkten Zeichensatz hast, der Output aber Bytes ist. Warum die Einschränkung, wenn es am Output nichts ändert, wenn man Bytes als Input hätte?
Für jedes Zeichen key.encode aufzurufen ist Rechenzeitverschwendung.
Bytes kann man zu druckbaren Zeichen konvertieren, z.B. per hex oder base64.
Wenn der Schlüssel nur bestimmte Bitmuster hat, dann ist die Verschlüsselung schwach. Bestimmte Kombinationen aus Schlüssel und Input können dann nur bestimmte Outputs liefern und damit ist die Entschlüsselung trivial.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Jankie: Das Grundproblem ist, dass man beliebige Bytes weder so ausgeben kann, dass der Benutzer die aus dem Terminal kopieren kann, noch kann man die vom Benutzer per Tastatur eingeben lassen.

Bei `CHARSET` hast Du ziemlich viele `list()`-Aufrufe. Man könnte da auch erst die ganzen Zeichenketten verbinden und dann *einmal* `list()` aufrufen. Und selbst das kann man weglassen, denn so eine Zeichenkette ist ja auch eine Sequenz die problemlos mit `random.choice()` funktioniert. Letztlich ist es aber Unsinnig den Schlüssel überhaupt so zu begrenzen. XOR ist hier eine Operation auf Bytewerten, da sollte man auch den gesamten Wertebereich abdecken.

Die XOR-Operation ist ja beim ver- und enschlüsseln gleich, trotzdem steht das bei Dir zweimal im Quelltext. Das sollte da nur einmal stehen. Dann müsste man es auch nur an einer Stelle ändern dass `bytes()` eine Liste bekommt, statt eines Generatorausdrucks.

Statt `enumerate()` und Indexzugriff, wäre das auch eher ein Fall für `operator.xor()` und `map()` mit drei Argumenten.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

@Sirius3: Meinst du mit dem beschränkten Zeichensatz als Input, dass ich die Umlaute umwandel? Und mit den bestimmten Bitmustern dass ich nicht alle Zeichen nutze? Wäre string.printable die bessere Wahl?

@__blackjack__: Also würde es Sinn machen alle Bytes in druckbare Zeichen umzuwandeln damit man diese kopieren kann und dann beim entschlüsseln wieder diese druckbaren Zeichen in Bytes umzuwandeln? Mit dem operator.xor() und map() werde ich mal versuchen, nur die drei Argumente verwirren mich.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Jankie: Oft sieht man `map()` nur mit zwei Argumenten. Man kann da aber mehr übergeben und das ist hier sehr nützlich.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich meine, dass Du aus 94 verschiedenen Input-Werten 128 verschiedene Outputwerte machst, was bedeutet, dass bestimmte Ausgabezeichen (z.B. das A) nur aus einer geringeren Anzahl an Inputzeichen (nähmlich 60) entstanden sein kann. Wenn ich die Nachricht knacken will, kann ich also 34 Zeichen ausschließen, was die "Sicherheit" Deiner Verschlüsselung drastisch reduziert.
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

@__blackjack__: Meinst du das so? map(operator.xor, message.encode(), key.encode())
@Sirius3: Also sollte ich für den Key auch alle 128 Werte benutzen die ich so erzeuge: ''.join(chr(i) for i in range(128))
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Jankie: Im Grunde schon, nur das an der Stelle IMHO `message` und `key` bereits Bytes sein sollten. XOR-Verschlüsselung ist auf Bytes definiert, nicht auf Zeichen.

Dementsprechend sollte auch nichts davon auf 7-Bit beschränkt sein. Und das erzeugen von aufsteigenden Bytewerten ist nicht wirklich sinnvoll wenn da dann mit `random.choice()` ausgewählt wird. Dazu braucht man die Werte nicht, denn das ist damit nur ein umständlich ausgedrücktes `randint()` oder `randrange()`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
DeaD_EyE
User
Beiträge: 1021
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Mit XOR sollte man nur arbeiten wenn:
  • Der Key etwa so lang ist wie die Eingabe
  • Der Key muss randomisiert sein, damit keine Wörterbuchattacken darauf möglich sind
  • Der Key darf nur für eine Nachricht verwendet werden.
Wenn nur einer der Punkte nicht erfüllt ist, ist es ziemlich einfach das zu knacken.
Auf StackOverflow bekommt man recht fundierte Antworten zu dem Thema.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

@__blackjack__:

Code: Alles auswählen

#Also statt:
bytes(map(operator.xor, message.encode(), key.encode()))

#sollte ich es lieber so machen?:
message_as_bytes = bytes(message)
key_as_bytes = bytes(key)
map(operator.xor, message_as_bytes, key_as_bytes)
Den letzten Satz versteh ich leider nicht ganz, wie soll ich das ansonsten lösen?

@DeaD_EyE:
Der Key sollte doch mindestens genau so lange sein und nicht in etwa gleich so lange, oder?
Und wirklich randomisiert geht ja nicht, gibt es da gute Alternativen?
Der letzte Punkt und der erste sollten ja gegeben sein.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Jankie: Naja, das geht ja nur mit `bytes()` wenn sowohl `message` als auch `key` ohne weiteres als Argument für `bytes()` verwendet werden können. Der Ansatz Text verschlüsseln zu wollen ist IMHO schon falsch. Mit XOR verschlüsselt man Bytes. Da kommen Bytes rein und Bytes raus. Es macht wenig Sinn Ein- oder Ausgabe auf irgendwelche Untermengen wie ASCII-Zeichen zu beschränken. Mit Bytes kann man *alles* verschlüsseln, auch Texte in beliebigen Kodierungen, aber eben auch wirklich *alles* andere.

Die Schnittstelle mit `input()` und `print()` funktioniert nicht für Binärdaten. Und wenn man die dafür beispielsweise Base64 kodiert — das gibt doch kein Mensch freiwillig manuell in einem Terminal ein.

Was verstehst Du konkret am letzten Satz nicht? Wie man das löst steht doch da: `randint()` oder `randrange()` statt `choice()` auf regelmässig aufsteigenden Werten in einer Sequenz. Beziehungsweise am besten gleich Bytes mit `os.urandom()` generieren.

Code: Alles auswählen

import os
from operator import xor


def _crypt(data, key):
    return bytes(map(xor, data, key))


def encrypt(data):
    key = os.urandom(len(data))
    return (_crypt(data, key), key)


decrypt = _crypt
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
DeaD_EyE
User
Beiträge: 1021
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Jankie hat geschrieben: Dienstag 17. November 2020, 15:33 Der Key sollte doch mindestens genau so lange sein und nicht in etwa gleich so lange, oder?
Ein Byte kürzer wäre nicht ganz so schlimm. Wenn der Key z.B. nur halb so lang ist, müsste man den Key zweimal wiederholen. Die Wiederholung schwächt die Verschlüsselung.
Und wirklich randomisiert geht ja nicht, gibt es da gute Alternativen?
Mit genügend Entropiequellen ist das kein Problem. Es Desktop-/ und auch Serverseystem haben ausreichend Entropiequellen.
Bei embedded Hardware ist der Zufall ein größeres Problem.

os.urandom liefert den größtmöglichen verfügbaren Zufall. Mittlerweile sollten alle Python-Versionen EOL sein, die das falsch gemacht haben.

Bei Kryptografie kann man viel falsch machen. Wenn man lernen will, implementiert man die Algorithmen selbst und wenn man es sicher haben will, verlässt man sich auf das, was da ist und ausreichend getestet worden ist.

Eins der vielen Projekte: https://cryptography.io/en/latest/
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten