Chiffrer et déchiffrer en utilisant PyCrypto AES 256

J'essaie de construire deux fonctions en utilisant PyCrypto qui acceptent deux paramètres: le message et la clé, puis chiffrer/déchiffrer le message.

, j'ai trouvé plusieurs liens sur le web pour m'aider, mais chacun d'eux a des défauts:

Celui - ci à codekoala utilise os.urandom, qui est découragé par PyCrypto.

De plus, la clé que je donne à la fonction n'est pas garantie d'avoir la longueur exacte attendue. Que puis-je faire pour que cela se produise ?

Aussi, il existe-t-il Plusieurs modes, lequel est recommandé? Je ne sais pas quoi utiliser :/

Enfin, qu'est-ce que L'IV exactement? Puis-je fournir un IV différent pour chiffrer et déchiffrer, ou cela retournera-t-il dans un résultat différent?

Voici ce que j'ai fait jusqu'à présent:

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

BLOCK_SIZE=32

def encrypt(message, passphrase):
    # passphrase MUST be 16, 24 or 32 bytes long, how can I do that ?
    IV = Random.new().read(BLOCK_SIZE)
    aes = AES.new(passphrase, AES.MODE_CFB, IV)
    return base64.b64encode(aes.encrypt(message))

def decrypt(encrypted, passphrase):
    IV = Random.new().read(BLOCK_SIZE)
    aes = AES.new(passphrase, AES.MODE_CFB, IV)
    return aes.decrypt(base64.b64decode(encrypted))
129
demandé sur Artjom B. 2012-09-21 09:54:41

9 réponses

Voici mon implémentation et fonctionne pour moi avec quelques corrections et améliore l'alignement de la phrase clé et secrète avec 32 octets et iv à 16 octets:

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

class AESCipher(object):

    def __init__(self, key): 
        self.bs = 32
        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))

    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:])]
100
répondu mnothic 2015-03-11 15:44:05

Vous pouvez avoir besoin des deux fonctions suivantes pour pad (quand chiffrer) et unpad(quand déchiffrer) lorsque la longueur d'entrée n'est pas un multiple de BLOCK_SIZE.

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

Donc vous demandez la longueur de la clé? Vous pouvez utiliser le md5sum de la clé plutôt que de l'utiliser directement.

Plus, selon ma petite expérience de L'utilisation de PyCrypto, le IV est utilisé pour mélanger la sortie d'un cryptage lorsque l'entrée est la même, donc le IV est choisi comme une chaîne aléatoire, et l'utiliser dans le cadre du cryptage sortie, puis l'utiliser pour déchiffrer le message.

Et voici ma mise en œuvre, j'espère que cela vous sera utile:

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:] ))
136
répondu Marcus 2014-10-23 19:28:57

Vous pouvez obtenir une phrase secrète d'un mot de passe arbitraire en utilisant une fonction de hachage cryptographique (Pas intégrée à Python hash) comme SHA-1 ou SHA-256. Python inclut la prise en charge des deux dans sa bibliothèque standard:

import hashlib

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

Vous pouvez tronquer une valeur de hachage cryptographique en utilisant [:16] ou [:24] et conserver sa sécurité jusqu'à la longueur que vous spécifiez.

6
répondu nneonneo 2012-09-21 06:08:30

Pour quelqu'un qui voudrait utiliser urlsafe_b64encode et urlsafe_b64decode, voici la version qui fonctionne pour moi (après avoir passé du temps avec le problème unicode)

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:]))
6
répondu Hoang HUA 2015-08-31 12:18:51

Pour le bénéfice des autres, voici mon implémentation de décryptage que j'ai obtenue en combinant les réponses de @ Cyril et @Marcus. Cela suppose que cela arrive via une requête HTTP avec le encryptedText cité et encodé en base64.

import base64
import urllib2
from Crypto.Cipher import AES


def decrypt(quotedEncodedEncrypted):
    key = 'SecretKey'

    encodedEncrypted = urllib2.unquote(quotedEncodedEncrypted)

    cipher = AES.new(key)
    decrypted = cipher.decrypt(base64.b64decode(encodedEncrypted))[:16]

    for i in range(1, len(base64.b64decode(encodedEncrypted))/16):
        cipher = AES.new(key, AES.MODE_CBC, base64.b64decode(encodedEncrypted)[(i-1)*16:i*16])
        decrypted += cipher.decrypt(base64.b64decode(encodedEncrypted)[i*16:])[:16]

    return decrypted.strip()
5
répondu scottmrogowski 2016-11-23 23:56:52

Laissez-moi répondre à votre question sur " modes."AES256 est une sorte de bloc de chiffrement . Il prend en entrée une clé de 32 octets et une chaîne de 16 octets, appelée block et génère un bloc. Nous utilisons AES dans un mode de fonctionnement afin de chiffrer. Les solutions ci-dessus suggèrent d'utiliser CBC, ce qui en est un exemple. Un autre est appelé CTR, et il est un peu plus facile à utiliser:

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)

Ceci est souvent appelé AES-CTR. je conseillerais la prudence en utilisant AES-CBC avec PyCrypto . La raison en est qu'il vous faut spécifier le schéma de remplissage , comme illustré par les autres solutions données. En général, si vous n'êtes pas très prudent sur le remplissage, Il y a des attaques qui cassent complètement le cryptage!

Maintenant, il est important de noter que la clé doit être une aléatoire, chaîne de 32 octets; un mot de passe ne suffire. Normalement, la clé est générée comme suit:

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

Une clé peut être dérivé d'un mot de passe, trop:

# 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)

Certaines solutions ci-dessus suggèrent d'utiliser SHA256 pour dériver la clé, mais ceci est généralement considéré mauvaise pratique cryptographique. Découvrez wikipedia pour en savoir plus sur les modes de fonctionnement.

5
répondu tweaksp 2017-06-20 20:54:14

Il est un peu tard mais je pense que cela sera très utile. Personne ne mentionne le schéma d'utilisation comme PKCS # 7 padding. Vous pouvez l'utiliser à la place des fonctions précédentes pour pad (quand faire le cryptage) et unpad (quand faire le décryptage).je vais fournir le Code source complet ci-dessous.

import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
import pkcs7
class Encryption:

    def __init__(self):
        pass

    def Encrypt(self, PlainText, SecurePassword):
        pw_encode = SecurePassword.encode('utf-8')
        text_encode = PlainText.encode('utf-8')

        key = hashlib.sha256(pw_encode).digest()
        iv = Random.new().read(AES.block_size)

        cipher = AES.new(key, AES.MODE_CBC, iv)
        pad_text = pkcs7.encode(text_encode)
        msg = iv + cipher.encrypt(pad_text)

        EncodeMsg = base64.b64encode(msg)
        return EncodeMsg

    def Decrypt(self, Encrypted, SecurePassword):
        decodbase64 = base64.b64decode(Encrypted.decode("utf-8"))
        pw_encode = SecurePassword.decode('utf-8')

        iv = decodbase64[:AES.block_size]
        key = hashlib.sha256(pw_encode).digest()

        cipher = AES.new(key, AES.MODE_CBC, iv)
        msg = cipher.decrypt(decodbase64[AES.block_size:])
        pad_text = pkcs7.decode(msg)

        decryptedString = pad_text.decode('utf-8')
        return decryptedString

import StringIO
import binascii


def decode(text, k=16):
    nl = len(text)
    val = int(binascii.hexlify(text[-1]), 16)
    if val > k:
        raise ValueError('Input is not padded or padding is corrupt')

    l = nl - val
    return text[:l]


def encode(text, k=16):
    l = len(text)
    output = StringIO.StringIO()
    val = k - (l % k)
    for _ in xrange(val):
        output.write('%02x' % val)
    return text + binascii.unhexlify(output.getvalue())

2
répondu xXxpRoGrAmmErxXx 2016-11-19 11:49:23

Une autre prise sur ceci (fortement dérivée des solutions ci-dessus) mais

  • utilise null pour le remplissage
  • n'utilise pas lambda (jamais été un fan)
  • Testé avec Python 2.7 et 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')
    
2
répondu MIkee 2018-09-15 22:22:34
from Crypto import Random
from Crypto.Cipher import AES
import base64

BLOCK_SIZE=16
def trans(key):
     return md5.new(key).digest()

def encrypt(message, passphrase):
    passphrase = trans(passphrase)
    IV = Random.new().read(BLOCK_SIZE)
    aes = AES.new(passphrase, AES.MODE_CFB, IV)
    return base64.b64encode(IV + aes.encrypt(message))

def decrypt(encrypted, passphrase):
    passphrase = trans(passphrase)
    encrypted = base64.b64decode(encrypted)
    IV = encrypted[:BLOCK_SIZE]
    aes = AES.new(passphrase, AES.MODE_CFB, IV)
    return aes.decrypt(encrypted[BLOCK_SIZE:])
0
répondu yuen 2016-08-11 13:24:12