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

171

J'essaie de créer deux fonctions en utilisant PyCrypto qui acceptent deux paramètres: le message et la clé, puis crypter / décrypter 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, ce qui est déconseillé 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 y parvenir?

De plus, il existe 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 le chiffrement et le déchiffrement, ou est-ce que le résultat sera différent?

Edit : Suppression de la partie de code car elle n'était pas sécurisée.

Cyril N.
la source
12
os.urandom est encouragé sur le site Web de PyCrypto . Il utilise la fonction CryptGenRandom de Microsoft qui est un CSPRNG
Joel Vroom
5
ou /dev/urandomsur Unix
Joel Vroom
2
Juste pour clarifier, dans cet exemple, la phrase de passe est la clé qui peut être de 128, 192 ou 256 bits (16, 24 ou 32 octets)
Marquez le
4
Il pourrait être utile de mentionner que PyCrypto est un projet mort . Le dernier commit date de 2014. PyCryptodome ressemble à un bon remplacement
Overdrivr
1
Cette question est ancienne, mais j'aimerais souligner (à partir de 2020) que pycrypto est probablement obsolète et n'est plus pris en charge. En regardant leur page github ( github.com/pycrypto/pycrypto ), il semble que leur dernier commit remonte à 2014. Je me méfierais d'utiliser un logiciel cryptographique qui n'est plus en développement
irritable_phd_synd du

Réponses:

151

Voici mon implémentation et fonctionne pour moi avec quelques correctifs et améliore l'alignement de la 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 = 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:])]
mnothique
la source
14
Je sais que cela dure depuis un certain temps, mais je pense que cette réponse peut semer la confusion. Cette fonction utilise une taille de bloc de 32 octets (256 octets) pour compléter les données d'entrée, mais AES utilise une taille de bloc de 128 bits. Dans AES256, la clé est de 256 bits, mais pas la taille du bloc.
Tannin
13
pour le dire autrement, "self.bs" doit être supprimé et remplacé par "AES.block_size"
Alexis
2
Pourquoi hachez-vous la clé? Si vous vous attendez à ce que ce soit quelque chose comme un mot de passe, vous ne devriez pas utiliser SHA256; mieux utiliser une fonction de dérivation de clé, comme PBKDF2, fournie par PyCrypto.
tweaksp
5
@Chris - SHA256 donne un hachage de 32 octets - une clé de taille parfaite pour AES256. La génération / dérivation d'une clé est supposée être aléatoire / sécurisée et doit être hors de la portée du code de cryptage / décryptage - le hachage est juste une garantie que la clé est utilisable avec le chiffre sélectionné.
zwer
2
dans _pad self.bs l'accès est nécessaire et dans _unpad pas besoin
mnothic
149

Vous pouvez avoir besoin des deux fonctions suivantes: pad- to pad (lors du cryptage) et unpad- pour unpad (lors du décryptage) 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:])]

Alors vous demandez la longueur de la clé? Vous pouvez utiliser la somme md5 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 cryptage lorsque l'entrée est la même, de sorte que l'IV est choisi comme une chaîne aléatoire, et l'utilise comme partie de la sortie de cryptage, puis utilisez-le pour déchiffrer le message.

Et voici ma mise en œuvre, j'espère qu'elle 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:] ))
Marcus
la source
1
Que se passe-t-il si vous avez une entrée qui est exactement un multiple de BLOCK_SIZE? Je pense que la fonction unpad deviendrait un peu confuse ...
Kjir
2
@Kjir, alors une séquence de valeur chr (BS) de longueur BLOCK_SIZE sera ajoutée aux données d'origine.
Marcus
1
@Marcus la padfonction est cassée (au moins dans Py3), remplacez-la par s[:-ord(s[len(s)-1:])]pour qu'elle fonctionne entre les versions.
Torxed le
2
La fonction @Torxed pad est disponible dans CryptoUtil.Padding.pad () avec pycryptodome (suivi de pycrypto)
comte
2
Pourquoi ne pas avoir simplement une constante de caractère comme caractère de remplissage?
Inaimathi
16

Permettez-moi de répondre à votre question sur les «modes». AES256 est une sorte de chiffrement par blocs . Il prend en entrée une 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 crypter. Les solutions ci-dessus suggèrent d'utiliser CBC, 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 oblige à spécifier le schéma de remplissage , comme illustré par les autres solutions données. En général, si vous ne faites pas très attention au rembourrage, il existe des attaques qui cassent complètement le cryptage!

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

# 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 également être dérivée d'un mot de passe :

# 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 cela est généralement considéré comme une mauvaise pratique cryptographique . Consultez wikipedia pour en savoir plus sur les modes de fonctionnement.

tweaksp
la source
iv_int = int (binascii.hexlify (iv), 16) ne fonctionne pas, remplacez-le par iv_int = int (binascii.hexlify (iv), 16) plus le 'import binascii' et cela devrait fonctionner (sur Python 3.x ), sinon excellent travail!
Valmond
Notez qu'il est préférable d'utiliser les modes de chiffrement automatique comme AES-GCM. GCM utilise en interne le mode CTR.
kelalaka le
Ce code cause "TypeError: Object type <class 'str'> ne peut pas être passé au code C"
Da Woon Jung
7

Pour quelqu'un qui aimerait 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:]))
Hoang HUA
la source
6

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 simplement en utilisant [:16]ou [:24]et elle conservera sa sécurité jusqu'à la longueur que vous spécifiez.

nneonneo
la source
13
Vous ne devez pas utiliser une fonction de hachage de la famille SHA pour générer une clé à partir d'un mot de passe - voir l'essai de Coda Hale sur le sujet . Pensez à utiliser une vraie fonction de dérivation de clé comme scrypt à la place. (L'essai de Coda Hale a été écrit avant la publication de scrypt.)
Benjamin Barenblat
7
Pour les futurs lecteurs, si vous cherchez à dériver une clé d'une phrase de passe, recherchez PBKDF2. Il est assez facile à utiliser en python ( pypi.python.org/pypi/pbkdf2 ). Si vous cherchez à hacher des mots de passe, cependant, bcrypt est une meilleure option.
C Fairweather
6

Je suis reconnaissant pour les autres réponses qui m'ont inspiré mais qui n'ont pas fonctionné pour moi.

Après avoir passé des heures à essayer de comprendre comment cela fonctionne, je suis venu avec la mise en œuvre ci - dessous avec la nouvelle PyCryptodomex bibliothèque (il est une autre histoire que je réussi à le mettre derrière proxy, sous Windows, dans un virtualenv .. ouf) de

travail sur votre implémentation, n'oubliez pas de noter les étapes de remplissage, d'encodage, de chiffrement (et vice versa). Vous devez emballer et déballer en gardant à l'esprit la commande.

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

__key__ = hashlib.sha256 (b'Clé de 16 caractères '). digest ()

def encrypt (brut):
    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)
    chiffrement = AES.new (clé = __key__, mode = AES.MODE_CFB, iv = iv)
    retourne base64.b64encode (iv + cipher.encrypt (raw))

def décrypter (enc):
    unpad = lambda s: s [: - ord (s [-1:])]

    enc = base64.b64decode (enc)
    iv = enc [: AES.block_size]
    chiffrement = AES.new (__ clé__, AES.MODE_CFB, iv)
    renvoie unpad (base64.b64decode (cipher.decrypt (enc [AES.block_size:])). decode ('utf8'))
cenkarioz
la source
Merci de bien vouloir pour un exemple de fonctionnement de ceci avec les bibliothèques PyCryptodomeX. C'est très utile!
Ygramul
5

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 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()
Scottmrogowski
la source
5

Une autre prise sur cela (fortement dérivée des solutions ci-dessus) 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')
MIkee
la source
Cela ne fonctionnera pas si l'octet d'entrée [] a des valeurs nulles à la fin car dans la fonction decrypt (), vous mangerez vos valeurs nulles de remplissage PLUS les valeurs nulles de fin.
Buzz Moschetti
Oui, comme je l'ai dit ci-dessus, ce bloc logique avec des valeurs nulles. Si les éléments que vous souhaitez encoder / décoder peuvent avoir des valeurs nulles à la fin, mieux vaut utiliser l'une des autres solutions ici
MIkee
3

J'ai utilisé les deux Cryptoet la PyCryptodomexbibliothèque et c'est très rapide ...

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 = AES.block_size

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)
Smack Alpha
la source
2

Il est peu tard mais je pense que ce sera très utile. Personne ne mentionne un schéma d'utilisation comme le rembourrage PKCS # 7. 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 fournirai 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())

Panagiotis Drakatos
la source
Je ne sais pas qui a décliné la réponse, mais je serais curieux de savoir pourquoi. Peut-être que cette méthode n'est pas sécurisée? Une explication serait géniale.
Cyril
1
@CyrilN. Cette réponse suggère que le hachage du mot de passe avec une seule invocation de SHA-256 est suffisant. Ça ne l'est pas. Vous devriez vraiment utiliser PBKDF2 ou similaire pour la dérivation de clé à partir d'un mot de passe en utilisant un grand nombre d'itérations.
Artjom B.
Merci pour le détail @ArtjomB.!
Cyril N.
J'ai une clé et une clé iv avec une longueur de 44. Comment puis-je utiliser vos fonctions?! tous les algorithmes sur Internet que j'ai trouvés ont un problème avec la longueur de ma clé vectorielle
mahshid.r
1
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:])
Yuen
la source
10
Veuillez fournir non seulement un code, mais également expliquer ce que vous faites et pourquoi c'est mieux / quelle est la différence avec les réponses existantes.
Florian Koch
Remplacez md5.new (clé) .digest () par md5 (clé) .digest (), et cela fonctionne comme un charme!
A STEFANI