PyCrypto AES 256 kullanarak şifreleme ve şifresini çözme


171

İki parametre kabul PyCrypto kullanarak iki işlev oluşturmaya çalışıyorum: ileti ve anahtar ve sonra şifrelemek / iletinin şifresini çözmek.

Bana yardımcı olmak için web'de birkaç bağlantı buldum, ancak her birinin kusurları var:

Codekoala'daki bu , PyCrypto tarafından önerilmeyen os.urandom'u kullanıyor.

Ayrıca, işleve verdiğim anahtarın beklenen tam uzunlukta olması garanti edilmez. Bunun olmasını sağlamak için ne yapabilirim?

Ayrıca, birkaç mod vardır, hangisi önerilir? Ne kullanacağımı bilmiyorum: /

Son olarak, IV tam olarak nedir? Şifreleme ve şifre çözme için farklı bir IV sağlayabilir miyim yoksa bu farklı bir sonuç getirir mi?

Düzenle : Güvenli olmadığı için kod bölümünü kaldırdık.


12
os.urandom edilir teşvik üzerinde PyCrypto web. Microsoft'un CSPRNG olan CryptGenRandom işlevini kullanır
Joel Vroom

5
veya /dev/urandomUnix'te
Joel Vroom

2
Sadece bu örnek içinde, netleştirmek için parola olan anahtar 128, 192, veya 256 bit (16, 24, veya 32 bayt) olabilir
Marka

4
PyCrypto'nun ölü bir proje olduğunu belirtmek gerekebilir . Son taahhüt 2014'ten. PyCryptodome iyi bir yedek oyuncu değişikliği gibi görünüyor
Overdrivr

1
Bu soru eski, ama (2020 itibariyle) pycrypto'nun muhtemelen eski ve artık desteklenmediğine dikkat çekmek istiyorum. Github sayfalarına ( github.com/pycrypto/pycrypto ) baktığımızda, son taahhütleri 2014 yılındaydı . Artık geliştirme aşamasında olmayan şifreleme yazılımı kullanma konusunda temkinli olurum
irritable_phd_syndrom

Yanıtlar:


151

İşte benim uygulama ve benim için bazı düzeltmeler ile çalışır ve anahtar ve gizli tümce 32 bayt ve iv 16 bayt ile hizalanmasını artırır:

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
Bunun bir süredir devam ettiğini biliyorum ama bu cevabın biraz karışıklığa neden olabileceğini düşünüyorum. Bu işlev giriş verilerini doldurmak için 32 baytlık (256 bayt) bir block_size kullanır ancak AES 128 bit blok boyutunu kullanır. AES256'da anahtar 256 bit, ancak blok boyutu değil.
Tannin

13
başka bir deyişle, "self.bs" kaldırılmalı ve "AES.block_size" ile değiştirilmelidir
Alexis

2
Anahtarı neden hash ediyorsun? Bunun bir şifre gibi bir şey olduğunu düşünüyorsanız, SHA256 kullanmamalısınız; PyCrypto'nun sağladığı PBKDF2 gibi bir anahtar türetme işlevini kullanmak daha iyidir.
tweaksp

5
@Chris - SHA256, 32 baytlık bir karma verir - AES256 için mükemmel bir anahtar. Bir anahtarın üretilmesi / türetilmesinin rastgele / güvenli olduğu varsayılır ve şifreleme / şifre çözme kodunun kapsamı dışında kalmalıdır - karma yalnızca anahtarın seçilen şifre ile kullanılabileceğinin garantisidir.
zwer

2
_pad self.bs erişimine ihtiyaç var ve _unpad'de gerek yok
mnothic

149

Aşağıdaki iki işleve gereksinim duyabilirsiniz: pad- unpadgiriş uzunluğu BLOCK_SIZE'ın katı olmadığında ped (şifreleme yaparken) ve - pedin açılması (şifre çözme yapılırken).

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

Yani anahtarın uzunluğunu mu soruyorsun? Anahtarın md5sum değerini doğrudan kullanmak yerine kullanabilirsiniz.

Daha fazla, PyCrypto kullanma deneyimime göre, IV, giriş aynı olduğunda bir şifreleme çıktısını karıştırmak için kullanılır, bu nedenle IV rastgele bir dize olarak seçilir ve şifreleme çıkışının bir parçası olarak kullanılır ve daha sonra Mesajın şifresini çözmek için kullanın.

Ve işte benim uygulama, umarım sizin için yararlı olacaktır:

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
BLOCK_SIZE değerinin tam katı olan bir girişiniz varsa ne olur? Ben unpad fonksiyonu biraz karışık olacağını düşünüyorum ...
Kjir

2
@Kjir, daha sonra BLOCK_SIZE uzunluğunda chr (BS) değeri dizisi başlangıç ​​verilerine eklenir.
Marcus

1
@Marcus padişlevi bozuldu (en azından Py3'te), s[:-ord(s[len(s)-1:])]sürümler arasında çalışması için ile değiştirin .
Şubat'ta Torxed

2
@Torxed yüzey işlevi pycryptodome (pycrypto takibinde) ile CryptoUtil.Padding.pad () 'de elde edilebilir olduğu
Kont

2
Neden sadece dolgu karakteri olarak bir karakter sabiti yok?
Inaimathi

16

"Modlar" hakkındaki sorunuza değineyim. AES256 bir çeşit blok şifredir . Giriş olarak 32 baytlık bir anahtar ve 16 baytlık bir blok alır , blok olarak adlandırılır ve bir blok çıkarır. Şifrelemek için AES'i bir çalışma modunda kullanıyoruz . Yukarıdaki çözümler, bir örnek olan CBC'nin kullanılmasını önermektedir. Bir başkasına TO denir ve kullanımı biraz daha kolaydır:

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)

Buna genellikle AES-TO denir. PyCrypto ile AES-CBC kullanırken dikkatli olmanızı tavsiye ederim . Bunun nedeni, verilen diğer çözümlerle örneklendiği gibi dolgu düzenini belirtmenizi gerektirmesidir . Eğer değilsen Genel olarak, çok dolgu konusunda dikkatli, orada saldırıları tamamen şifrelemeyi kırmak!

Şimdi, anahtarın 32 baytlık rastgele bir dize olması gerektiğini belirtmek önemlidir ; bir şifre yeterli değil . Normalde, anahtar şöyle üretilir:

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

Anahtar bir paroladan da türetilebilir :

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

Yukarıdaki bazı çözümler, anahtarın türetilmesi için SHA256'nın kullanılmasını önermektedir, ancak bu genellikle kötü şifreleme uygulaması olarak kabul edilir . Çalışma modları hakkında daha fazla bilgi için wikipedia'ya göz atın .


iv_int = int (binascii.hexlify (iv), 16) çalışmaz, iv_int = int (binascii.hexlify (iv), 16) artı 'içe aktarma binascii' ile değiştirin ve çalışması gerekir (Python 3.x üzerinde) ), aksi takdirde harika bir iş!
Valmond

Yetkilendirilmiş Şifreleme modlarını AES-GCM olarak kullanmanın daha iyi olduğunu unutmayın. GCM dahili olarak TO modunu kullanır.
kelalaka

Bu kod, "TypeError: <class 'str'> nesne türü C koduna geçirilemiyor"
Da Woon Jung

7

Urlsafe_b64encode ve urlsafe_b64decode kullanmak isteyen biri için işte benim için çalışan sürüm (unicode sorunu ile biraz zaman geçirdikten sonra)

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

SHA-1 veya SHA-256 gibi bir şifreleme sağlama işlevini ( Python'un yerleşik DEĞİLhash ) kullanarak rasgele bir paroladan parola alabilirsiniz . Python, standart kütüphanesinde her ikisi için de destek içerir:

import hashlib

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

Bir şifreleme karma değerini yalnızca [:16]veya tuşunu kullanarak [:24]kısaltabilirsiniz ve güvenliğini belirttiğiniz uzunluğa kadar koruyacaktır.


13
Bir paroladan anahtar oluşturmak için SHA ailesi karma işlevini kullanmamalısınız - Coda Hale'in konuyla ilgili makalesine bakın . Bunun yerine scrypt gibi gerçek bir anahtar türetme işlevi kullanmayı düşünün . (Coda Hale'in makalesi,
scrypt'in

7
Gelecekteki okuyucular için, bir paroladan bir anahtar türetmek istiyorsanız, PBKDF2'yi arayın. Python'da kullanımı oldukça kolaydır ( pypi.python.org/pypi/pbkdf2 ). Bununla birlikte, karma şifreleri arıyorsanız, bcrypt daha iyi bir seçenektir.
C Fairweather

6

İlham veren ama benim için çalışmayan diğer cevaplar için minnettarım.

Nasıl çalıştığını anlamaya çalışıyorum saat geçirdikten sonra ben yeni aşağıda uygulanması ile geldi PyCryptodomex kütüphanesinden (ben bir Virtualenv .. vay be içinde, Windows üzerinde, Proxy'yle kurmak için yönetilen nasıl başka bir hikaye)

üzerine Çalışma uygulama, doldurma, kodlama, şifreleme adımları yazmayı unutmayın (ve tersi). Siparişi aklınızda tutarak paketlemeniz ve paketinden çıkarmanız gerekir.

ithalat tabanı64
hashlib'i içe aktar
Cryptodome.Cipher ithalat AES
Rasgele içe aktarma get_random_bytes

__key__ = hashlib.sha256 (b'16 karakter tuşu '). özet ()

def şifrelemek (ham):
    BS = AES.block_size
    ped = 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)
    şifre = AES.new (anahtar = __key__, mod = AES.MODE_CFB, iv = iv)
    return base64.b64encode (iv + cipher.encrypt (ham))

def şifresini çözme (enc):
    unpad = lambda s: s [: - ord (s [-1:])]

    enc = base64.b64decode (enc)
    iv = enc [: AES.block_size]
    şifre = AES.new (__ key__, AES.MODE_CFB, iv)
    unpad'i döndür (base64.b64decode (cipher.decrypt (enc [AES.block_size:])). kod çözme ('utf8'))

PyCryptodomeX kütüphaneleri ile bunun işlevsel bir örneği için teşekkür ederiz. Bu oldukça yardımcı!
Ygramul

5

Başkalarının yararına, @Cyril ve @Marcus'un cevaplarını birleştirerek elde ettiğim şifre çözme uygulamam. Bunun, şifrelenmiş Metin ve base64 kodlu HTTP İsteği ile geldiğini varsayar.

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

Buna bir kez daha (yukarıdaki çözümlerden yoğun olarak türetilmiş) ama

  • dolgu için null kullanır
  • lambda kullanmaz (asla hayran olmamıştır)
  • python 2.7 ve 3.6.5 ile test edilmiştir

    #!/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')

Eğer dectept () fonksiyonunda dolgu null'larınızı PLUS herhangi bir sondaki null değerini yiyeceğiniz için, girdi baytında [] sondaki boş değerler varsa bu çalışmaz.
Buzz Moschetti

Evet, yukarıda belirttiğim gibi, bu mantık null değerine sahip. Kodlamak / kod çözmek istediğiniz öğelerin sonunda boş değerler varsa, buradaki diğer çözümlerden birini daha iyi kullanın
MIkee

3

Hem kullanmış Cryptove PyCryptodomexkütüphane ve hızlı yanan bir ...

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

Biraz geç ama bence bu çok yardımcı olacak. Hiç kimse PKCS # 7 dolgu gibi kullanım şemasından bahsetmiyor. Bunun yerine önceki işlevleri şifrelemek (şifreleme yaparken) ve kilidini açmak için (şifre çözme yaparken) kullanabilirsiniz. İ aşağıda tam Kaynak Kodunu sağlayacaktır.

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


Cevabı kimin düşürdüğünü bilmiyorum ama nedenini merak ediyorum. Belki bu yöntem güvenli değildir? Bir açıklama harika olurdu.
Cyril

1
@CyrilN. Bu cevap, şifreyi tek bir SHA-256 çağrısı ile karıştırmanın yeterli olduğunu göstermektedir. Öyle değil. Büyük bir yineleme sayısı kullanarak bir paroladan anahtar türetmek için PBKDF2 veya benzeri bir şey kullanmalısınız.
Artjom B.Mar

@ArtjomB detay için teşekkür ederim.!
Cyril

44 uzunluğunda bir anahtarım ve iv anahtarım var. İşlevlerinizi nasıl kullanabilirim?! İnternette bulduğum tüm algoritmalar, vektör anahtarımın uzunluğu ile ilgili sorun
yaşıyor


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
Lütfen yalnızca kod sağlamakla kalmaz, aynı zamanda ne yaptığınızı ve bunun neden daha iyi olduğunu / mevcut cevaplar arasındaki farkın ne olduğunu da açıklayın.
Florian Koch

Md5.new (key) .digest () öğesini md5 (key) .digest () ile değiştirin ve bir cazibe gibi çalışır!
Bir STEFANI
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.