Wie funktioniert AES in CTR für Python mit PyCrypto?
-
12-12-2019 - |
Frage
Ich verwende Python 2.7.1 Ich möchte STH mit AES im CTR -Modus verschlüsseln.Ich habe die PyCrypto-Bibliothek für Python installiert.Ich habe den folgenden Code geschrieben:
secret = os.urandom(16)
crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=lambda: secret)
encrypted = crypto.encrypt("asdk")
print crypto.decrypt(encrypted)
Ich muss crypto.decrypt so oft ausführen, wie mein Klartext Bytes groß ist, um die entschlüsselten Daten korrekt zu erhalten.D.h.:
encrypted = crypto.encrypt("test")
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)
Der letzte Aufruf zum Entschlüsseln gibt mir den Klartext zurück.Die anderen Ausgaben von decrypt sind einige Kauderwelsch-Strings.Ich frage mich, ob das normal ist oder nicht?Muss ich jedes Mal eine Schleife mit der Größe meines Klartextes einbinden, oder habe ich etwas falsch gemacht?
Lösung
Laut @gertvdijk ist AES_CTR eine Stream-Verschlüsselung, die dies tut nicht brauchen Polsterung.Daher habe ich die entsprechenden Codes gelöscht.
Hier ist etwas, das ich weiß.
Sie müssen denselben Schlüssel verwenden (den ersten Parameter in
AES.new(...)
) bei der Ver- und Entschlüsselung und halten Sie den Schlüssel geheim.Die Verschlüsselungs-/Entschlüsselungsmethoden sind Staatsbürgerlich, das bedeutet
crypto.en(de)crypt("abcd")==crypto.en(de)crypt("abcd")
Ist nicht immer wahr.In Ihrer CTR gibt Ihr Counter-Callback immer das Gleiche zurück, sodass er beim Verschlüsseln zustandslos wird (ich bin nicht 100 % sicher, dass das der Grund ist), aber wir stellen immer noch fest, dass er bei der Entschlüsselung einigermaßen zustandsbehaftet ist.Zusammenfassend lässt sich sagen, dass wir dafür immer ein neues Objekt verwenden sollten.Der
counter callback
Die Funktion sollte sich sowohl bei der Verschlüsselung als auch bei der Entschlüsselung gleich verhalten.In Ihrem Fall geht es darum, dass beide das gleiche Geheimnis zurückgeben.Dennoch glaube ich nicht, dasssecret
ist ein Geheimnis".Sie können einen Zufallsgenerator verwenden"secret"
und geben Sie es ohne Verschlüsselung an die kommunizierenden Peers weiter, sodass die andere Seite es direkt verwenden kann, sofern dies der Fall istsecret
Ist nicht vorhersehbar.
Deshalb würde ich meine Chiffre so schreiben und hoffe, dass sie etwas Hilfe bietet.
import os
import hashlib
import Crypto.Cipher.AES as AES
class Cipher:
@staticmethod
def md5sum( raw ):
m = hashlib.md5()
m.update(raw)
return m.hexdigest()
BS = AES.block_size
@staticmethod
def pad( s ):
"""note that the padding is no necessary"""
"""return s + (Cipher.BS - len(s) % Cipher.BS) * chr(Cipher.BS - len(s) % Cipher.BS)"""
return s
@staticmethod
def unpad( s ):
"""return s[0:-ord(s[-1])]"""
return s
def __init__(self, key):
self.key = Cipher.md5sum(key)
#the state of the counter callback
self.cnter_cb_called = 0
self.secret = None
def _reset_counter_callback_state( self, secret ):
self.cnter_cb_called = 0
self.secret = secret
def _counter_callback( self ):
"""
this function should be stateful
"""
self.cnter_cb_called += 1
return self.secret[self.cnter_cb_called % Cipher.BS] * Cipher.BS
def encrypt(self, raw):
secret = os.urandom( Cipher.BS ) #random choose a "secret" which is not secret
self._reset_counter_callback_state( secret )
cipher = AES.new( self.key, AES.MODE_CTR, counter = self._counter_callback )
raw_padded = Cipher.pad( raw )
enc_padded = cipher.encrypt( raw_padded )
return secret+enc_padded #yes, it is not secret
def decrypt(self, enc):
secret = enc[:Cipher.BS]
self._reset_counter_callback_state( secret )
cipher = AES.new( self.key, AES.MODE_CTR, counter = self._counter_callback )
enc_padded = enc[Cipher.BS:] #we didn't encrypt the secret, so don't decrypt it
raw_padded = cipher.decrypt( enc_padded )
return Cipher.unpad( raw_padded )
Einige Tests:
>>> from Cipher import Cipher
>>> x = Cipher("this is key")
>>> "a"==x.decrypt(x.encrypt("a"))
True
>>> "b"==x.decrypt(x.encrypt("b"))
True
>>> "c"==x.decrypt(x.encrypt("c"))
True
>>> x.encrypt("a")==x.encrypt("a")
False #though the input is same, the outputs are different
Referenz: http://packages.python.org/pycrypto/Crypto.Cipher.blockalgo-module.html#MODE_CTR
Andere Tipps
Ich werde auf @ Gertvdijks Erklärung, warum die Verschlüsselung aufwendig ist, warum sich die Zeichnung benimmt, wie es in der ursprünglichen Frage (meine Bearbeitung abgelehnt wurde), aber auch darauf hinweisen, dass die Einrichtung des Zählers zum Zurückgeben eines statischen Werts ein Major ist Fehler und zeigen Sie, wie Sie es richtig einrichten.
Setzen Sie den Zähler für neue Operationen
zurückder Grund, warum sich dies verhält, wie Sie sich in der Frage beschrieben haben, ist, dass Ihr einfacher Text (4 Bytes / 32 Bit) viermal so klein ist, wie die Größe des Schlüsselstroms blockiert, dass die CTR-Chips für die Verschlüsselung (16 Bytes / 128 Bits).
Da Sie anstelle eines tatsächlichen Zählers denselben festen Wert verwenden, spuckt die Chiffre den gleichen 16-Byte-Blöcke von TESTREAM aus. Sie können dies beobachten, indem Sie wiederholt 16 NULL-Bytes verschlüsseln:
generasacodicetagpre.Sie setzen den Status des Chiffres auch nicht vor, bevor Sie die Entschlüsselung durchführen, sodass die 4 Bytes von CEHRTEXT von den nächsten 4 Bytes der XOR-Taste vom ersten Ausgangsstromblock entschlüsselt werden. Dies kann auch durch Verschlüsseln und Entschlüsseln von Null-Bytes beachtet werden:
generasacodicetagpre.Wenn dies so arbeiten würde, wie Sie wollten, sollte das Ergebnis beider beiden Vorgängen gleich sein. Stattdessen können Sie die ersten vier Bytes des 16 Byteblocks im ersten Ergebnis sehen, und die zweiten vier Bytes im zweiten Ergebnis.
Nachdem Sie den 16 Byte-Block der XOR-Taste aufgebraucht haben, indem Sie vier Operationen auf vier-Byte-Werten (für 16 Byte-Total) durchführen, wird ein neuer Block der XOR-Taste erzeugt. Die ersten vier Bytes (sowie alle anderen) jedes XOR-Schlüsselblocks sind gleich, also wenn Sie diesmal entschlüsseln, gibt es den Klartext zurück.
Lösung
Sie müssen den Zustand der Chiffre zurücksetzen, bevor Sie einen Betrieb in einem neuen Datenstrom (oder einem anderen Vorgang darauf) durchführen, da die ursprüngliche Instanz nicht mehr im richtigen Anfangszustand liegt. Ihr Problem wird gelöst, indem ein neues crypto
-Objekt für die Entschlüsselung instanziiert wird, sowie das Zurücksetzen der Zähler- und Keystream-Position.
Sie müssen auch eine ordnungsgemäße Zählerfunktion verwenden , die eine Nonce mit einem Zählerwert kombiniert, der jedes Mal steigt, wenn ein neuer Block von Keystream erzeugt wird. Pycrypto hat eine Counter-Klasse, die dies für Sie tun kann.
generasacodicetagpre.Beginnen Sie mit einem neuen Kryptoobjekt für neue Vorgänge
Der Grund dafür, dass sich das so verhält, wie Sie es in der Frage beschrieben haben, liegt darin, dass Ihr Klartext (4 Bytes/32 Bits) viermal so klein ist wie die Größe, mit der die kryptografische Engine für den von Ihnen gewählten AES-Modus arbeitet (128 Bits) und diese auch wiederverwendet Instanz des Kryptoobjekts.Verwenden Sie einfach nicht dasselbe Objekt wieder, wenn Sie eine Operation an einem neuen Datenstrom (oder eine andere Operation daran) ausführen.Ihr Problem wird durch die Instanziierung einer neuen Lösung gelöst crypto
Objekt für die Entschlüsselung, etwa so:
# *NEVER* USE A FIXED LIKE COUNTER BELOW IN PRODUCTION CODE. READ THE DOCS.
counter = os.urandom(16)
key = os.urandom(32) # 256 bits key
# Instantiate a crypto object first for encryption
encrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
encrypted = encrypto.encrypt("asdk")
# Instantiate a new crypto object for decryption
decrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
print decrypto.decrypt(encrypted) # prints "asdk"
Warum es nicht um das Auffüllen mit AES-CTR geht
Diese Antwort begann als Antwort auf die Antwort von Marcus, in dem er zunächst darauf hinwies, dass die Verwendung einer Polsterung das Problem lösen würde.Ich verstehe zwar, dass es wie Symptome eines Polsterungsproblems aussieht, aber das ist ganz sicher nicht der Fall.
Der springende Punkt bei AES-CTR ist, dass Sie brauchen keine Polsterung, da es ein ist Stream Chiffre (im Gegensatz zu ECB/CBC usw.)!Stromchiffren arbeiten mit Datenströmen, indem sie Daten in Blöcke aufteilen und diese in der eigentlichen kryptografischen Berechnung verketten.
Zusätzlich zu dem, was Marcus sagt, der Crypto.Util.Counter
Klasse kann verwendet werden, um Ihre Zählerblockfunktion zu erstellen.