Crittografare e decrittografare utilizzando PyCrypto AES 256


171

Sto cercando di creare due funzioni utilizzando PyCrypto che accettano due parametri: il messaggio e la chiave, quindi crittografare / decrittografare il messaggio.

Ho trovato diversi link sul web per aiutarmi, ma ognuno di essi ha dei difetti:

Questo a codekoala usa os.urandom, che è scoraggiato da PyCrypto.

Inoltre, il tasto che do alla funzione non è garantito per avere la lunghezza esatta prevista. Cosa posso fare per farlo accadere?

Inoltre, ci sono diverse modalità, quale è consigliata? Non so cosa usare: /

Infine, cos'è esattamente il IV? Posso fornire un IV diverso per la crittografia e la decrittografia o restituirà un risultato diverso?

Modifica : rimossa la parte di codice poiché non era sicura.


12
os.urandom è incoraggiato sul sito Web di PyCrypto . Utilizza la funzione CryptGenRandom di Microsoft che è un CSPRNG
Joel Vroom,

5
o /dev/urandomsu Unix
Joel Vroom il

2
Giusto per chiarire, in questo esempio la passphrase è la chiave che può essere 128, 192 o 256 bit (16, 24 o 32 byte)
Mark

4
Vale la pena ricordare che PyCrypto è un progetto morto . L'ultimo commit è del 2014. PyCryptodome sembra una buona sostituzione drop-in
Overdrivr

1
Questa domanda è vecchia, ma vorrei sottolineare (dal 2020) che pycrypto è probabilmente obsoleto e non è più supportato. Guardando la loro pagina github ( github.com/pycrypto/pycrypto ), sembra che il loro ultimo commit sia stato nel 2014. Sarei diffidente nell'utilizzare software crittografico che non è più in fase di sviluppo
irritable_phd_syndrom

Risposte:


151

Ecco la mia implementazione e funziona per me con alcune correzioni e migliora l'allineamento della chiave e della frase segreta con 32 byte e iv a 16 byte:

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:])]

14
So che questo è successo da un po ', ma penso che questa risposta possa diffondere un po' di confusione. Questa funzione utilizza un block_size di 32 byte (256 byte) per riempire i dati di input, ma AES utilizza una dimensione di blocco a 128 bit. In AES256 la chiave è 256 bit, ma non la dimensione del blocco.
Tannin,

13
per dirla in altro modo, "self.bs" dovrebbe essere rimosso e sostituito da "AES.block_size"
Alexis,

2
Perché hai l'hashing della chiave? Se ti aspetti che sia qualcosa come una password, non dovresti usare SHA256; meglio usare una funzione di derivazione dei tasti, come PBKDF2, fornita da PyCrypto.
tweaksp

5
@Chris - SHA256 emette un hash a 32 byte - una chiave di dimensioni perfette per AES256. La generazione / derivazione di una chiave è considerata casuale / sicura e dovrebbe essere al di fuori dell'ambito del codice di crittografia / decrittazione; l'hash è solo una garanzia che la chiave è utilizzabile con la cifra selezionata.
zwer,

2
in _pad self.bs è necessario l'accesso e in _unpad non è necessario
mnothic

149

Potrebbero essere necessarie le due seguenti funzioni: pad- pad (quando si esegue la crittografia) e unpad- pad (quando si esegue la decrittografia) quando la lunghezza dell'input non è un multiplo di 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:])]

Quindi stai chiedendo la lunghezza della chiave? È possibile utilizzare md5sum della chiave anziché utilizzarla direttamente.

Inoltre, secondo la mia piccola esperienza nell'uso di PyCrypto, IV viene utilizzato per confondere l'output di una crittografia quando l'input è lo stesso, quindi IV viene scelto come stringa casuale e usarlo come parte dell'output di crittografia, quindi usalo per decifrare il messaggio.

Ed ecco la mia implementazione, spero che ti sarà 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:] ))

1
Cosa succede se hai un input che è esattamente un multiplo di BLOCK_SIZE? Penso che la funzione di sblocco sarebbe un po 'confusa ...
Kjir

2
@Kjir, quindi una sequenza di valore chr (BS) di lunghezza BLOCK_SIZE verrà aggiunta ai dati di origine.
Marco

1
@Marcus la padfunzione è rotta (almeno in Py3), sostituirla con s[:-ord(s[len(s)-1:])]per farla funzionare tra le versioni.
Torxato il

2
La funzione @Torxed pad è disponibile in CryptoUtil.Padding.pad () con pycryptodome (pycrypto followup)
comte

2
Perché non avere solo una costante di carattere come carattere di riempimento?
Inaimathi,

16

Vorrei rispondere alla tua domanda sulle "modalità". AES256 è una specie di cifra cifrata . Prende come input una chiave da 32 byte e una stringa da 16 byte, chiamata blocco e genera un blocco. Utilizziamo AES in una modalità operativa per crittografare. Le soluzioni sopra suggeriscono l'utilizzo di CBC, che è un esempio. Un altro si chiama CTR ed è un po 'più facile da usare:

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)

Questo è spesso indicato come AES-CTR. Consiglierei cautela nell'uso di AES-CBC con PyCrypto . Il motivo è che richiede di specificare lo schema di riempimento , come esemplificato dalle altre soluzioni fornite. In generale, se non stai molto attento all'imbottitura, ci sono attacchi che interrompono completamente la crittografia!

Ora, è importante notare che la chiave deve essere una stringa casuale a 32 byte ; una password non è sufficiente. Normalmente, la chiave viene generata in questo modo:

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

Una chiave può essere derivata anche da una password :

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

Alcune soluzioni sopra suggeriscono di utilizzare SHA256 per ricavare la chiave, ma questa è generalmente considerata una cattiva pratica crittografica . Dai un'occhiata a Wikipedia per ulteriori informazioni sulle modalità di funzionamento.


iv_int = int (binascii.hexlify (iv), 16) non funziona, sostituiscilo con iv_int = int (binascii.hexlify (iv), 16) più "import binascii" e dovrebbe funzionare (su Python 3.x ), altrimenti ottimo lavoro!
Valmond,

Si noti che è meglio utilizzare le modalità di crittografia Autehnticated come AES-GCM. GCM utilizza internamente la modalità CTR.
Kelalaka,

Questo codice causa "TypeError: il tipo di oggetto <class 'str'> non può essere passato al codice C"
Da Woon Jung

7

Per qualcuno che desidera utilizzare urlsafe_b64encode e urlsafe_b64decode, ecco la versione che funziona per me (dopo aver trascorso un po 'di tempo con il problema dell'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

È possibile ottenere una passphrase da una password arbitraria utilizzando una funzione hash crittografica ( NON incorporata in Python hash) come SHA-1 o SHA-256. Python include il supporto per entrambi nella sua libreria 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

È possibile troncare un valore hash crittografico semplicemente usando [:16]o [:24]e manterrà la sua sicurezza fino alla lunghezza specificata.


13
Non dovresti usare una funzione hash della famiglia SHA per generare una chiave da una password - vedi il saggio di Coda Hale sull'argomento . Prendi invece in considerazione l'utilizzo di una vera funzione di derivazione della chiave come scrypt . (Il saggio di Coda Hale è stato scritto prima della pubblicazione di
Scrypt

7
Per i lettori futuri, se stai cercando di ricavare una chiave da una passphrase, cerca PBKDF2. È abbastanza facile da usare in Python ( pypi.python.org/pypi/pbkdf2 ). Se stai cercando password hash, tuttavia, bcrypt è un'opzione migliore.
C Fairweather,

6

Grato per le altre risposte che hanno ispirato ma non ha funzionato per me.

Dopo aver trascorso ore a cercare di capire come funziona, mi si avvicinò con l'attuazione di sotto con la più recente PyCryptodomex libreria (è un'altra storia come sono riuscito a configurarlo dietro proxy, su Windows, in un virtualenv .. uff)

di lavoro sulla l'implementazione, ricordati di annotare i punti di riempimento, codifica e crittografia (e viceversa). Devi imballare e disimballare tenendo presente l'ordine.

import base64
import hashlib
da Cryptodome.Cipher import AES
da Cryptodome.Importazione casuale get_random_bytes

__key__ = hashlib.sha256 (chiave di 16 caratteri '). 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)
    cifra = AES.new (chiave = __key__, modalità = 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'))

Grazie per un esempio funzionante di questo con le librerie PyCryptodomeX. È abbastanza utile!
Ygramul

5

A beneficio di altri, ecco la mia implementazione della decrittazione che ho ottenuto combinando le risposte di @Cyril e @Marcus. Ciò presuppone che ciò avvenga tramite la richiesta HTTP con il criptato Testo citato e la base64 codificato.

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

Un'altra opinione su questo (fortemente derivata dalle soluzioni sopra) ma

  • usa null per il riempimento
  • non usa lambda (mai stato un fan)
  • testato con Python 2.7 e 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')

Questo non funzionerà se il byte di input [] ha valori null finali perché nella funzione decrypt () mangerete i vostri valori di riempimento PIÙ eventuali null finali.
Buzz Moschetti,

Sì, come ho affermato sopra, questa logica si riempie di null. Se gli articoli che vuoi codificare / decodificare potrebbero avere null finali, meglio usare una delle altre soluzioni qui
MIkee

3

Ho usato entrambi Cryptoe PyCryptodomexlibreria ed è velocissimo ...

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)

2

È un po 'tardi, ma penso che sarà molto utile. Nessuno menziona schemi di utilizzo come l'imbottitura PKCS # 7. Puoi usarlo invece delle funzioni precedenti per pad (quando esegui la crittografia) e unpad (quando esegui la decrittografia) .I fornirà il codice sorgente completo di seguito.

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


Non so chi abbia votato in negativo la risposta, ma sarei curioso di sapere perché. Forse questo metodo non è sicuro? Una spiegazione sarebbe fantastica.
Cyril N.

1
@CyrilN. Questa risposta suggerisce che è sufficiente l'hash della password con una singola chiamata di SHA-256. Non lo è. Dovresti davvero usare PBKDF2 o simile per la derivazione della chiave da una password usando un conteggio di iterazioni elevato.
Artjom B.

Grazie per il dettaglio @ArtjomB.!
Cyril N.

Ho una chiave e anche una chiave iv con 44 lunghezze. Come posso usare le tue funzioni ?! tutti gli algoritmi in Internet che ho trovato, hanno problemi con la lunghezza della mia chiave vettoriale
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:])

10
Fornisci non solo il codice, ma spiega anche cosa stai facendo e perché è meglio / qual è la differenza rispetto alle risposte esistenti.
Florian Koch,

Sostituisci md5.new (key) .digest () con md5 (key) .digest () e funziona come un incantesimo!
A STEFANI
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.