Verschlüsseln und Entschlüsseln mit PyCrypto AES 256

Lesezeit: 11 Minuten

Benutzer-Avatar
Kyrill N.

Ich versuche, zwei Funktionen mit PyCrypto zu erstellen, die zwei Parameter akzeptieren: die Nachricht und den Schlüssel, und dann die Nachricht verschlüsseln/entschlüsseln.

Ich habe mehrere Links im Internet gefunden, die mir helfen, aber jeder von ihnen hat Fehler:

Dieses hier bei codekoala verwendet os.urandom, was von PyCrypto abgeraten wird.

Außerdem ist nicht garantiert, dass der Schlüssel, den ich der Funktion gebe, genau die erwartete Länge hat. Was kann ich tun, damit das passiert?

Außerdem gibt es mehrere Modi, welcher ist empfehlenswert? Ich weiß nicht was ich nehmen soll :/

Schließlich, was genau ist die IV? Kann ich eine andere IV zum Verschlüsseln und Entschlüsseln angeben oder führt dies zu einem anderen Ergebnis?

Bearbeiten: Der Codeteil wurde entfernt, da er nicht sicher war.

  • os.urandom ist ermutigte auf der PyKrypto Webseite. Es verwendet Microsofts CryptGenRandom Funktion, die a ist CSPRNG

    – Joel Vroom

    28. November 2013 um 15:43 Uhr

  • oder /dev/urandom auf Unix

    – Joel Vroom

    28. November 2013 um 16:19 Uhr

  • Nur zur Verdeutlichung, in diesem Beispiel Passphrase ist der Schlüssel die 128, 192 oder 256 Bit (16, 24 oder 32 Byte) sein können

    – Markieren

    21. April 2016 um 21:37 Uhr

  • Es könnte erwähnenswert sein, dass PyCrypto eine ist totes Projekt. Der letzte Commit ist von 2014. PyCryptodome sieht nach einem guten Drop-in-Ersatz aus

    – Overdriver

    7. August 2018 um 7:58 Uhr

  • Diese Frage ist alt, aber ich möchte (ab 2020) darauf hinweisen, dass Pycrypto wahrscheinlich veraltet ist und nicht mehr unterstützt wird. Ein Blick auf ihre Github-Seite (github.com/pycrypto/pycrypto), scheint ihr letzter Commit im Jahr 2014 gewesen zu sein. Ich wäre misstrauisch gegenüber der Verwendung von kryptografischer Software, die nicht mehr in der Entwicklung ist

    – irritable_phd_syndrome

    6. Juni 2020 um 3:40 Uhr


Benutzer-Avatar
mnotisch

Hier ist meine Implementierung und funktioniert für mich mit einigen Korrekturen und verbessert die Ausrichtung des Schlüssels und der geheimen Phrase mit 32 Bytes und iv auf 16 Bytes:

import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES

class AESCipher(object):

    def __init__(self, key): 
        self.bs = AES.block_size
        self.key = hashlib.sha256(key.encode()).digest()

    def encrypt(self, raw):
        raw = self._pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw.encode()))

    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        iv = enc[:AES.block_size]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')

    def _pad(self, s):
        return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s)-1:])]

  • Warum hashst du den Schlüssel? Wenn Sie erwarten, dass dies so etwas wie ein Passwort ist, sollten Sie SHA256 nicht verwenden. Verwenden Sie besser eine Schlüsselableitungsfunktion wie PBKDF2, die PyCrypto bereitstellt.

    – zwicksp

    20. Juni 2017 um 18:43 Uhr

  • @Chris – SHA256 gibt einen 32-Byte-Hash aus – ein Schlüssel mit perfekter Größe für AES256. Es wird davon ausgegangen, dass die Generierung/Ableitung eines Schlüssels zufällig/sicher ist und außerhalb des Bereichs des Verschlüsselungs-/Entschlüsselungscodes liegen sollte – Hashing ist nur eine Garantie dafür, dass der Schlüssel mit der ausgewählten Chiffre verwendbar ist.

    – zwer

    20. Juni 2017 um 20:35 Uhr

  • @mnothic Was ist der Grund für _pad eine normale Klassenmethode und _unpad sein staticmethod? Was ist der Unterschied?

    – Boffin

    4. Juli 2017 um 16:11 Uhr

  • in _pad ist self.bs Zugriff erforderlich und in _unpad nicht erforderlich

    – mnotisch

    5. Juli 2017 um 13:56 Uhr

  • @mnothic – dumme Frage: warum s[:-ord(s[len(s)-1:])] statt nur s[:-ord(s[-1])]?

    – Ryugie

    30. Oktober 2017 um 19:15 Uhr

Benutzer-Avatar
Markus

Möglicherweise benötigen Sie die folgenden zwei Funktionen: pad– zum Auffüllen (bei der Verschlüsselung) und unpad– zum Auffüllen (bei der Entschlüsselung), wenn die Länge der Eingabe kein Vielfaches von BLOCK_SIZE ist.

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[:-ord(s[len(s)-1:])]

Du fragst also nach der Schlüssellänge? Sie können die MD5-Summe des Schlüssels verwenden, anstatt sie direkt zu verwenden.

Darüber hinaus wird der IV nach meiner kleinen Erfahrung mit PyCrypto verwendet, um die Ausgabe einer Verschlüsselung zu verwechseln, wenn die Eingabe gleich ist, sodass die IV als zufällige Zeichenfolge ausgewählt und als Teil der Verschlüsselungsausgabe verwendet wird, und dann Verwenden Sie es, um die Nachricht zu entschlüsseln.

Und hier ist meine Implementierung, ich hoffe, sie wird für Sie nützlich sein:

import base64
from Crypto.Cipher import AES
from Crypto import Random

class AESCipher:
    def __init__( self, key ):
        self.key = key

    def encrypt( self, raw ):
        raw = pad(raw)
        iv = Random.new().read( AES.block_size )
        cipher = AES.new( self.key, AES.MODE_CBC, iv )
        return base64.b64encode( iv + cipher.encrypt( raw ) ) 

    def decrypt( self, enc ):
        enc = base64.b64decode(enc)
        iv = enc[:16]
        cipher = AES.new(self.key, AES.MODE_CBC, iv )
        return unpad(cipher.decrypt( enc[16:] ))

  • Was passiert, wenn Sie eine Eingabe haben, die genau ein Vielfaches von BLOCK_SIZE ist? Ich denke, dass die Unpad-Funktion etwas verwirrt werden würde …

    – Kjir

    21. Oktober 2013 um 10:43 Uhr

  • @Kjir, dann wird eine Sequenz des Wertes chr(BS) in der Länge BLOCK_SIZE an die Ursprungsdaten angehängt.

    – Markus

    22. Oktober 2013 um 4:14 Uhr

  • @Marcus der pad Funktion ist defekt (zumindest in Py3), ersetzen Sie durch s[:-ord(s[len(s)-1:])] damit es versionsübergreifend funktioniert.

    – Torx

    24. Februar 2014 um 11:06 Uhr

  • @Torxed-Pad-Funktion ist verfügbar in CryptoUtil.Padding.pad() mit pycryptodome (pycrypto followup)

    – comte

    11. April 2017 um 15:46 Uhr

  • Warum nicht einfach eine Zeichenkonstante als Füllzeichen verwenden?

    – Inaimathi

    25. April 2017 um 14:48 Uhr

Benutzer-Avatar
optimieren

Lassen Sie mich auf Ihre Frage zu “Modi” eingehen. AES256 ist eine Art Blockchiffre. Es nimmt als Eingabe ein 32-Byte Schlüssel und eine 16-Byte-Zeichenfolge namens the Block und gibt einen Block aus. Wir verwenden AES in a Arbeitsweise um zu verschlüsseln. Die oben genannten Lösungen schlagen die Verwendung von CBC vor, was ein Beispiel ist. Eine andere heißt CTR und ist etwas einfacher zu verwenden:

from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto import Random

# AES supports multiple key sizes: 16 (AES128), 24 (AES192), or 32 (AES256).
key_bytes = 32

# Takes as input a 32-byte key and an arbitrary-length plaintext and returns a
# pair (iv, ciphtertext). "iv" stands for initialization vector.
def encrypt(key, plaintext):
    assert len(key) == key_bytes

    # Choose a random, 16-byte IV.
    iv = Random.new().read(AES.block_size)

    # Convert the IV to a Python integer.
    iv_int = int(binascii.hexlify(iv), 16) 

    # Create a new Counter object with IV = iv_int.
    ctr = Counter.new(AES.block_size * 8, initial_value=iv_int)

    # Create AES-CTR cipher.
    aes = AES.new(key, AES.MODE_CTR, counter=ctr)

    # Encrypt and return IV and ciphertext.
    ciphertext = aes.encrypt(plaintext)
    return (iv, ciphertext)

# Takes as input a 32-byte key, a 16-byte IV, and a ciphertext, and outputs the
# corresponding plaintext.
def decrypt(key, iv, ciphertext):
    assert len(key) == key_bytes

    # Initialize counter for decryption. iv should be the same as the output of
    # encrypt().
    iv_int = int(iv.encode('hex'), 16) 
    ctr = Counter.new(AES.block_size * 8, initial_value=iv_int)

    # Create AES-CTR cipher.
    aes = AES.new(key, AES.MODE_CTR, counter=ctr)

    # Decrypt and return the plaintext.
    plaintext = aes.decrypt(ciphertext)
    return plaintext

(iv, ciphertext) = encrypt(key, 'hella')
print decrypt(key, iv, ciphertext)

Dies wird oft als AES-CTR bezeichnet. Ich rate zur Vorsicht bei der Verwendung von AES-CBC mit PyCrypto. Der Grund dafür ist, dass Sie die angeben müssen Polsterschema, wie durch die anderen angegebenen Lösungen veranschaulicht. Im Allgemeinen, wenn Sie nicht sind sehr Achte auf die Polsterung, es gibt Anschläge die die Verschlüsselung vollständig brechen!

Nun ist es wichtig zu beachten, dass der Schlüssel a sein muss zufälliger 32-Byte-String; ein Passwort nicht genügen. Normalerweise wird der Schlüssel wie folgt generiert:

# Nominal way to generate a fresh key. This calls the system's random number
# generator (RNG).
key1 = Random.new().read(key_bytes)

Ein Schlüssel kann sein von einem Passwort abgeleitetzu:

# It's also possible to derive a key from a password, but it's important that
# the password have high entropy, meaning difficult to predict.
password = "This is a rather weak password."

# For added # security, we add a "salt", which increases the entropy.
#
# In this example, we use the same RNG to produce the salt that we used to
# produce key1.
salt_bytes = 8 
salt = Random.new().read(salt_bytes)

# Stands for "Password-based key derivation function 2"
key2 = PBKDF2(password, salt, key_bytes)

Einige der oben genannten Lösungen schlagen vor, SHA256 zum Ableiten des Schlüssels zu verwenden, aber dies wird im Allgemeinen in Betracht gezogen schlechte kryptografische Praxis. Kasse Wikipedia für mehr zu den Betriebsarten.

  • iv_int = int(binascii.hexlify(iv), 16) funktioniert nicht, ersetzen Sie es durch iv_int = int(binascii.hexlify(iv), 16) plus ‘import binascii’ und es sollte funktionieren (unter Python 3.x ), ansonsten tolle Arbeit!

    – Walmond

    20. Mai 2019 um 15:04 Uhr

  • Beachten Sie, dass es besser ist, Autehnticated Encryption-Modi als AES-GCM zu verwenden. GCM verwendet intern den CTR-Modus.

    – kelalaka

    15. September 2019 um 10:02 Uhr


  • Dieser Code verursacht “TypeError: Objekttyp kann nicht an C-Code übergeben werden”

    – Da Woon Jung

    21. November 2019 um 2:49 Uhr

  • @DaWoonJung Ich habe dasselbe mit Python 3.9.7 beobachtet. ciphertext = aes.encrypt(plaintext.encode(‘utf-8’)) und die Geheimtext bräuchte die decodieren (‘utf-8’) um den str, bytes Roundtrip abzuschließen.

    – Stefan Scheller

    1. Dezember 2021 um 7:55 Uhr


Benutzer-Avatar
cenkarioz

Dankbar für die anderen Antworten, die mich inspirierten, aber nicht funktionierten.

Nachdem ich Stunden damit verbracht hatte, herauszufinden, wie es funktioniert, kam ich auf die Implementierung unten mit der neuesten PyCryptodomex Bibliothek (es ist eine andere Geschichte, wie ich es hinter Proxy unter Windows in einer virtuellen Umgebung eingerichtet habe. Puh)

Denken Sie bei der Arbeit an Ihrer Implementierung daran, die Schritte zum Auffüllen, Codieren und Verschlüsseln aufzuschreiben (und umgekehrt). Beim Ein- und Auspacken muss die Reihenfolge beachtet werden.

import base64
import hashlib
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes

__key__ = hashlib.sha256(b'16-character key').digest()

def encrypt(raw):
    BS = AES.block_size
    pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

    raw = base64.b64encode(pad(raw).encode('utf8'))
    iv = get_random_bytes(AES.block_size)
    cipher = AES.new(key= __key__, mode= AES.MODE_CFB,iv= iv)
    return base64.b64encode(iv + cipher.encrypt(raw))

def decrypt(enc):
    unpad = lambda s: s[:-ord(s[-1:])]

    enc = base64.b64decode(enc)
    iv = enc[:AES.block_size]
    cipher = AES.new(__key__, AES.MODE_CFB, iv)
    return unpad(base64.b64decode(cipher.decrypt(enc[AES.block_size:])).decode('utf8'))

Für jemanden, der urlsafe_b64encode und urlsafe_b64decode verwenden möchte, hier ist die Version, die für mich funktioniert (nachdem ich einige Zeit mit dem Unicode-Problem verbracht habe)

BS = 16
key = hashlib.md5(settings.SECRET_KEY).hexdigest()[:BS]
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[:-ord(s[len(s)-1:])]

class AESCipher:
    def __init__(self, key):
        self.key = key

    def encrypt(self, raw):
        raw = pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.urlsafe_b64encode(iv + cipher.encrypt(raw)) 

    def decrypt(self, enc):
        enc = base64.urlsafe_b64decode(enc.encode('utf-8'))
        iv = enc[:BS]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return unpad(cipher.decrypt(enc[BS:]))

Benutzer-Avatar
nneonneo

Sie können aus einem beliebigen Passwort eine Passphrase erhalten, indem Sie eine kryptografische Hash-Funktion verwenden (NICHT Python ist eingebaut hash) wie SHA-1 oder SHA-256. Python unterstützt beides in seiner Standardbibliothek:

import hashlib

hashlib.sha1("this is my awesome password").digest() # => a 20 byte string
hashlib.sha256("another awesome password").digest() # => a 32 byte string

Sie können einen kryptografischen Hashwert einfach mit abschneiden [:16] oder [:24] und es behält seine Sicherheit bis zu der von Ihnen angegebenen Länge.

Eine andere Einstellung dazu (stark von den obigen Lösungen abgeleitet), aber

  • verwendet null zum Auffüllen
  • verwendet kein Lambda (war nie ein Fan)
  • getestet mit Python 2.7 und 3.6.5

    #!/usr/bin/python2.7
    # you'll have to adjust for your setup, e.g., #!/usr/bin/python3
    
    
    import base64, re
    from Crypto.Cipher import AES
    from Crypto import Random
    from django.conf import settings
    
    class AESCipher:
        """
          Usage:
          aes = AESCipher( settings.SECRET_KEY[:16], 32)
          encryp_msg = aes.encrypt( 'ppppppppppppppppppppppppppppppppppppppppppppppppppppppp' )
          msg = aes.decrypt( encryp_msg )
          print("'{}'".format(msg))
        """
        def __init__(self, key, blk_sz):
            self.key = key
            self.blk_sz = blk_sz
    
        def encrypt( self, raw ):
            if raw is None or len(raw) == 0:
                raise NameError("No value given to encrypt")
            raw = raw + '\0' * (self.blk_sz - len(raw) % self.blk_sz)
            raw = raw.encode('utf-8')
            iv = Random.new().read( AES.block_size )
            cipher = AES.new( self.key.encode('utf-8'), AES.MODE_CBC, iv )
            return base64.b64encode( iv + cipher.encrypt( raw ) ).decode('utf-8')
    
        def decrypt( self, enc ):
            if enc is None or len(enc) == 0:
                raise NameError("No value given to decrypt")
            enc = base64.b64decode(enc)
            iv = enc[:16]
            cipher = AES.new(self.key.encode('utf-8'), AES.MODE_CBC, iv )
            return re.sub(b'\x00*$', b'', cipher.decrypt( enc[16:])).decode('utf-8')
    

  • Dies funktioniert nicht, wenn das Eingangsbyte[] hat nachgestellte Nullen, weil Sie in der decrypt () -Funktion Ihre Füllnullen PLUS alle nachgestellten Nullen essen.

    – Buzz Moschetti

    31. Dezember 2017 um 18:48 Uhr

  • Ja, wie ich oben gesagt habe, diese Logik füllt mit Nullen. Wenn die Elemente, die Sie codieren/decodieren möchten, nachgestellte Nullen haben können, verwenden Sie besser eine der anderen Lösungen hier

    – Mikee

    1. Januar 2018 um 22:12 Uhr

  • ** Warnung ** Diese Funktion funktioniert nicht mit lateinischen und Sonderzeichen.

    – Elias Prado

    18. Oktober 2021 um 13:11 Uhr

1033330cookie-checkVerschlüsseln und Entschlüsseln mit PyCrypto AES 256

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy