web-dev-qa-db-fra.com

Cryptage Python avec PyCrypto AES

Je viens de trouver pycrypto aujourd'hui et je travaille sur ma classe de chiffrement AES. Malheureusement, cela ne fonctionne qu'à moitié. self.h.md5 génère un hachage md5 au format hexadécimal (32 octets ..__). Il semble déchiffrer le message, mais il met des caractères aléatoires après le déchiffrement, dans ce cas\n\n\n ... Je pense avoir un problème avec la taille de bloc de self.data, tout le monde sait comment résoudre ce problème?

Jans-MacBook-Pro: test2 jan $ ../../bin/python3 data.py b'RLfGmn5jf5WTJphnmW0hXG7IaIYCCRpjaTTqwXR6yiCUytnDib + GQYlFORm + jIctest. 1 2 3 4 5 test final\n\n\n\n\n\n\n\n\n\n\n 'n

from Crypto.Cipher import AES
from base64 import b64encode, b64decode
from os import urandom

class Encryption():
    def __init__(self):
        self.h = Hash()

    def values(self, data, key):
        self.data = data
        self.key = key
        self.mode = AES.MODE_CBC
        self.iv = urandom(16)
        if not self.key:
            self.key = Cfg_Encrypt_Key
        self.key = self.h.md5(self.key, True)

    def encrypt(self, data, key):
        self.values(data, key)
        return b64encode(self.iv + AES.new(self.key, self.mode, self.iv).encrypt(self.data))

    def decrypt(self, data, key):
        self.values(data, key)
        self.iv = b64decode(self.data)[:16]
        return AES.new(self.key, self.mode, self.iv).decrypt(b64decode(self.data)[16:])
14

Pour être honnête, les caractères "\ n\n\n\n\n\n\n\n\n\n\n" ne me semblent pas aussi aléatoires. ;-)

Vous utilisez AES en mode CBC. Cela nécessite que la longueur du texte en clair et du texte chiffré soit toujours un multiple de 16 octets. Avec le code que vous montrez, vous devriez réellement voir une exception être levée lorsque data passé à encrypt() ne remplit pas cette condition. Il semble que vous ayez ajouté suffisamment de nouveaux caractères de ligne ( '\ n' à quelque entrée que ce soit jusqu'à ce que le texte en clair soit aligné.

En dehors de cela, il existe deux manières courantes de résoudre le problème de l'alignement:

  1. Basculez de CBC (AES.MODE_CBC) à CFB (AES.MODE_CFB). Avec le segment_size par défaut utilisé par PyCrypto, vous n’aurez aucune restriction quant à la longueur du texte en clair et du texte chiffré.

  2. Conservez CBC et utilisez un schéma de remplissage tel que PKCS # 7, à savoir:

    • avant de chiffrer un texte en clair de X octets, ajoutez à l'arrière autant d'octets nécessaires pour atteindre la limite de 16 octets suivante. Tous les octets de remplissage ont la même valeur: le nombre d'octets que vous ajoutez:

      length = 16 - (len(data) % 16)
      data += bytes([length])*length
      

      C'est le style Python 3. En Python 2, vous auriez:

      length = 16 - (len(data) % 16)
      data += chr(length)*length
      
    • après le déchiffrement, supprimez à l'arrière du texte en clair le nombre d'octets indiqué par le remplissage:

      data = data[:-data[-1]]
      

Même si je comprends dans votre cas qu’il s’agit simplement d’un exercice en classe, je voudrais souligner qu’il n’est pas sûr d’envoyer des données sans aucune forme d’authentification (par exemple un MAC).

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

def derive_key_and_iv(password, salt, key_length, iv_length):
    d = d_i = ''
    while len(d) < key_length + iv_length:
        d_i = md5(d_i + password + salt).digest()
        d += d_i
    return d[:key_length], d[key_length:key_length+iv_length]

def encrypt(in_file, out_file, password, key_length=32):
    bs = AES.block_size
    salt = Random.new().read(bs - len('Salted__'))
    key, iv = derive_key_and_iv(password, salt, key_length, bs)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    #print in_file
    in_file = file(in_file, 'rb')
    out_file = file(out_file, 'wb')
    out_file.write('Salted__' + salt)
    finished = False
    while not finished:
        chunk = in_file.read(1024 * bs)
        if len(chunk) == 0 or len(chunk) % bs != 0:
            padding_length = bs - (len(chunk) % bs)
            chunk += padding_length * chr(padding_length)
            finished = True
        out_file.write(cipher.encrypt(chunk))
    in_file.close()
    out_file.close()

def decrypt(in_file, out_file, password, key_length=32):
    bs = AES.block_size

    in_file = file(in_file, 'rb')
    out_file = file(out_file, 'wb')
    salt = in_file.read(bs)[len('Salted__'):]
    key, iv = derive_key_and_iv(password, salt, key_length, bs)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    next_chunk = ''
    finished = False
    while not finished:
        chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
        if len(next_chunk) == 0:
            padding_length = ord(chunk[-1])
            if padding_length < 1 or padding_length > bs:
               raise ValueError("bad decrypt pad (%d)" % padding_length)
            # all the pad-bytes must be the same
            if chunk[-padding_length:] != (padding_length * chr(padding_length)):
               # this is similar to the bad decrypt:evp_enc.c from openssl program
               raise ValueError("bad decrypt")
            chunk = chunk[:-padding_length]
            finished = True
        out_file.write(chunk)    
    in_file.close()
    out_file.close()

def encode(in_file, out_file):
    in_file = file(in_file, 'rb')
    out_file = file(out_file, 'wb')
    data = in_file.read()
    out_file.write(base64.b64encode(data))    
    in_file.close()
    out_file.close()

def decode(in_file, out_file):
    in_file = file(in_file, 'rb')
    out_file = file(out_file, 'wb')
    data = in_file.read()
    out_file.write(base64.b64decode(data))    
    in_file.close()
    out_file.close()
0
Abhisheietk

Vous pouvez utiliser un caractère de correction aussi longtemps que vous vous souvenez de la longueur de votre charge utile initiale, afin de ne pas "jeter" les octets de fin utiles ... ..

import base64

from Crypto.Cipher import AES

def encrypt(payload, salt, key):
    return AES.new(key, AES.MODE_CBC, salt).encrypt(r_pad(payload))


def decrypt(payload, salt, key, length):
    return AES.new(key, AES.MODE_CBC, salt).decrypt(payload)[:length]


def r_pad(payload, block_size=16):
    length = block_size - (len(payload) % block_size)
    return payload + chr(length) * length


print(decrypt(encrypt("some cyphertext", "b" * 16, "b" * 16), "b" * 16, "b" * 16, len("some cyphertext")))