web-dev-qa-db-fra.com

Crypter et déchiffrer à l'aide de PyCrypto AES 256

J'essaie de créer deux fonctions à l'aide de 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'entre eux a des défauts:

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

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

En outre, il existe plusieurs modes, lequel est recommandé? Je ne sais pas quoi utiliser: /

Enfin, quel est exactement le IV? Puis-je fournir un IV différent pour le cryptage et le décryptage, ou est-ce que cela renverra 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))
136
Cyril N.

Voici mon implémentation et fonctionne pour moi avec quelques corrections et améliore l'alignement de la phrase clé et de la phrase 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:])]
109
mnothic

Vous aurez peut-être besoin des deux fonctions suivantes pour remplir (lorsque le chiffrement) et unpad (pour le déchiffrement) lorsque la longueur de l'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.

De plus, selon ma petite expérience d'utilisation de PyCrypto, l'IV est utilisé pour mélanger la sortie d'un chiffrement lorsque l'entrée est identique, de sorte que l'IV est choisi comme chaîne aléatoire et utilisé utilisez-le pour déchiffrer le message.

Et voici ma mise en œuvre, espérons 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:] ))
139
Marcus

Permettez-moi de répondre à votre question sur les "modes". AES256 est une sorte de bloc chiffré. Il prend en entrée un clé de 32 octets et une chaîne de 16 octets, appelée le bloc 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, qui est un exemple. Un autre s'appelle 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 vous conseillerais de ne pas utiliser AES-CBC avec PyCrypto. La raison en est qu'il vous oblige à spécifier le 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 attaques qui cassent complètement le cryptage! 

A présent, il est important de noter que la clé doit être une chaîne aléatoire de 32 octets}; un mot de passe non suffit. 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ée d'un mot de passe, aussi:

# 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é comme mauvaise pratique cryptographique . Consultez wikipedia pour plus d'informations sur les modes de fonctionnement.

7
tweaksp

Pour ceux qui souhaitent utiliser urlsafe_b64encode et urlsafe_b64decode, voici la version qui fonctionne pour moi (après avoir passé un peu de 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
Hoang HUA

Vous pouvez obtenir une phrase secrète d'un mot de passe arbitraire en utilisant une fonction de hachage cryptographique (NOThash de Python) comme SHA-1 ou SHA-256. Python inclut un support pour les 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 simplement [:16] ou [:24] et il conservera sa sécurité jusqu'à la longueur spécifiée.

6
nneonneo

Pour le bénéfice des autres, voici mon implémentation de décryptage à laquelle je suis arrivé en combinant les réponses de @Cyril et @Marcus. Cela suppose que cela vienne via une requête HTTP avec encryptedText cité et codé 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
scottmrogowski

Une autre prise en compte (fortement inspirée des solutions précédentes) mais

  • utilise null pour le remplissage 
  • n'utilise pas de lambda (jamais été 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
MIkee

Il est un peu tard, mais je pense que cela sera très utile. Personne ne mentionne un schéma d'utilisation tel que le rembourrage PKCS # 7. Vous pouvez l'utiliser à la place des fonctions précédentes pour pad (lorsque le chiffrement) et unpad (pour le déchiffrement) .i fournira 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

Reconnaissant pour les autres réponses qui ont inspiré mais qui n'ont pas fonctionné pour moi. 

Après avoir passé des heures à comprendre comment cela fonctionne, j’ai proposé l’implémentation ci-dessous avec la dernière bibliothèque PyCryptodomex (c’est une autre histoire de savoir comment j’ai réussi à l’installer derrière un proxy, sous Windows, dans un environnement virtuel. phew) 

Lorsque vous travaillez sur votre implémentation, n'oubliez pas de noter les étapes de remplissage, d'encodage et de cryptage (et inversement). Vous devez emballer et décompresser en tenant compte de la commande.

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

__ clé__ = hashlib.sha256 (clé de 16 caractères '). () 

 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 (clé = __key__, mode = AES.MODE_CFB, iv = iv) 
 retourne 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) 
 renvoyer unpad (base64.b64decode (cipher.decrypt (enc [AES.block_size:])). decode ('utf8')) 
1
cenkarioz

J'ai utilisé à la fois la bibliothèque Crypto et PyCryptodomex et elle flambe vite ...

import base64
import hashlib
from Cryptodome.Cipher import AES as domeAES
from Cryptodome.Random import get_random_bytes
from Crypto import Random
from Crypto.Cipher import AES as cryptoAES


BLOCK_SIZE=16

key = "my_secret_key".encode()
__key__ = hashlib.sha256(key).digest()
print(__key__)


def encrypt(raw):
    BS = cryptoAES.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(cryptoAES.block_size)
    cipher = cryptoAES.new(key= __key__, mode= cryptoAES.MODE_CFB,iv= iv)
    a= base64.b64encode(iv + cipher.encrypt(raw))
    IV = Random.new().read(BLOCK_SIZE)
    aes = domeAES.new(__key__, domeAES.MODE_CFB, IV)
    b = base64.b64encode(IV + aes.encrypt(a))
    return b

def decrypt(enc):
    passphrase = __key__
    encrypted = base64.b64decode(enc)
    IV = encrypted[:BLOCK_SIZE]
    aes = domeAES.new(passphrase, domeAES.MODE_CFB, IV)
    enc = aes.decrypt(encrypted[BLOCK_SIZE:])
    unpad = lambda s: s[:-ord(s[-1:])]
    enc = base64.b64decode(enc)
    iv = enc[:cryptoAES.block_size]
    cipher = cryptoAES.new(__key__, cryptoAES.MODE_CFB, iv)
    b=  unpad(base64.b64decode(cipher.decrypt(enc[cryptoAES.block_size:])).decode('utf8'))
    return b

encrypted_data =encrypt("Hi Steven!!!!!")
print(encrypted_data)
print("=======")
decrypted_data = decrypt(encrypted_data)
print(decrypted_data)
1
Smack Alpha
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
yuen

https://stackoverflow.com/a/21928790/11402877

encodage compatible utf-8

def _pad(self, s):
    s = s.encode()
    res = s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs).encode()
    return res
0
sixiyizai