Wir können nun beliebige Zeichenfolge codieren und mit einer XOR-Operation verschlüsseln. Die Verschlüsselung erfolgt dabei blockweise, wobei jeder Block genau die Länge des Schlüssels hat. Die Verschlüsselung folgt diesem Schema:
Beispielsweise verschlüsseln wir einen Text mit dem Schlüssel ZUSE
:
|EINE| SEH|R KL|EINE| SEN|SATI|ON|
XOR |ZUSE|ZUSE|ZUSE|ZUSE|ZUSE|ZUSE|ZU|
|@-. |ZFVM|HUXI|@-. |ZFVK|ITGL|U,|
Was fällt dabei auf?
Lösung:
Warum ist das ein Problem?
Lösung:
Moderne Kryptographie-Verfahren funktionieren ganz ähnlich, aber es gibt ein paar wesentliche Unterschiede:
Statt einer simplen XOR Operation wird bei der Verschlüsselung jedes Blocks eine kompliziertere Funktion verwendet. Das Ziel dabei ist, dass jeweils nicht nur ein einzelnes Bit des Schlüssels und des Klartexts kombiniert werden, sondern dass möglichst alle Bits miteinander vermischt werden. Das führt dazu, dass ein geänderter Buchstabe dazu führt, dass alle anderen Stellen des Blocks ebenfalls verändert werden.
Wir haben aber trotzdem noch ein Problem: komplett gleiche Blöcke werden weiterhin gleich verschlüsselt. Wiederholt sich eine Klartext-Sequenz exakt mit der Blockgrösse, wird das Chiffrat ebenfalls an diesen Stellen gleich sein, was einem Angreifer auffallen dürfte. Zudem ist es für einen Angreifer nach wie vor möglich, verschlüsselte Blöcke einzufügen oder Blöcke zu löschen, ohne dass dies detektiert werden kann.
Aus diesem Grund werden die Blöcke nochmals miteinander verkettet (en. chaining): der Ciphertext des vorherigen Blocks wird mit dem Klartext des momentanen Blocks mit einer XOR-Operation verbunden. Dadurch ist jeder Block immer auch an den vorherigen Block gebunden, eine Änderung eines Buchstabens zu Beginn der Nachricht ändert auch alle folgenden Blöcke.
Der Unterschied zwischen der Verschlüsselung ohne oder mit Chaining kann anhand eines verschlüsselten Bildes illustriert werden: Das zu verschlüsselnde Bild enthält viele weisse Pixel (= gleiche Blöcke). Bei entsprechender Blocklänge erscheinen die gleich verschlüsselten Blöcke immerzu im Ciphertext, und lassen übers ganze das Bild deutlich erscheinen. Im Gegensatz dazu enthält das Bild nur Rauschen, wenn es im Chaining-Modus verschlüsselt wurde (Quelle: Wikipedia: Electronic Code Book Mode):
Nimm den Full-Block-Coder aus der Lösung der Aufgabe 4 und baue die Verkettung ein. Modifiziere sowohl die Verschlüsselung und die Entschlüsselung. Als Zumischung für den ersten Block definierst du einen Initialization Vector (s. Abbildung).
Lösung:
- chaining_block_coder.py
def textToBytes(text):
"""Converts a string into a list of ASCII codes."""
numbers = []
for letter in text:
number = ord(letter)
numbers.append(number)
return numbers
def bytesToText(numbers):
"""Converts a number list into text using ASCII decoding."""
text = ""
for number in numbers:
letter = chr(number)
text += letter
return text
def binaryToBytes(binary):
"""Converts a sequence of binary strings into a list of numbers."""
numbers = []
for bin in binary:
number = int(bin, 2)
numbers.append(number)
return numbers
def bytesToBinary(numbers):
"""Converts a number list into a space separated binary string."""
text = ""
for number in numbers:
binary = format(number, "b")
text += binary + " "
return text
def parseWords(binaryText):
"""Splits a string with space-separated words into a list of words."""
return binaryText.split()
def xor(block, key):
"""Computes XOR for a single block of numbers."""
result = []
for i in range(len(block)):
result.append(block[i] ^ key[i])
return result
def decryptBlock(block, key, previous_block):
return xor(xor(block, key), previous_block)
def decrypt(ciphertext, passphrase):
"""Decrypts a sequence of binary text using a passphrase"""
ciphernumbers = binaryToBytes(parseWords(ciphertext))
key = textToBytes(passphrase)
plaintext = []
# Content does not matter - we'll discard the first block
previous_block = textToBytes("1234567890")
# Split ciphertext into blocks of key length, then decrypt each block.
first = True
for i in range(0, len(ciphernumbers), len(key)):
cipherblock = ciphernumbers[i:i+len(key)]
plainblock = decryptBlock(cipherblock, key, previous_block)
if first:
first = False
else:
plaintext += plainblock
previous_block = cipherblock
return bytesToText(plaintext)
def encryptBlock(block, key, previous_block):
return xor(xor(block, previous_block), key)
def encrypt(plaintext, passphrase):
"""Encrypts plaintext as binary string output using passphrase."""
key = textToBytes(passphrase)
plaincodes = textToBytes(plaintext)
# First block is random - decrypt will discard it.
first_block = textToBytes("abcdefghij")
# Initialization vector - in reality, we'd choose a random value.
initialization_vector = textToBytes("9876543210")
# Encrypt the random first block with the init vector - it will be
# discarded.
ciphertext = encryptBlock(first_block, key, initialization_vector)
previous_block = ciphertext
# Split plaintext into blocks of key length, then encrypt each block.
for i in range(0, len(plaincodes), len(key)):
plainblock = plaincodes[i:i+len(key)]
cipherblock = encryptBlock(plainblock, key, previous_block)
ciphertext += cipherblock
previous_block = cipherblock
return bytesToBinary(ciphertext)
key = "ROMANSHORN"
print(decrypt(encrypt("Das ist alles wirlich sehr, sehr geheim!", key), key))