使用PyCrypto AES 256加密和解密

Cyr*_* N. 153 python encryption padding pycrypto initialization-vector

我正在尝试使用PyCrypto构建两个函数,它们接受两个参数:消息和密钥,然后加密/解密消息.

我在网上发现了几个链接来帮助我,但每个链接都有缺陷:

在codekoala上的这个使用os.urandom,PyCrypto不鼓励这样做.

而且,我给函数的关键不能保证具有预期的确切长度.我该怎么做才能实现这一目标?

此外,还有几种模式,建议使用哪种模式?我不知道该用什么:/

最后,究竟是什么?我可以为加密和解密提供不同的IV,还是会以不同的结果返回?

这是我到目前为止所做的:

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))
Run Code Online (Sandbox Code Playgroud)

Mar*_*cus 142

当输入长度不是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:])]
Run Code Online (Sandbox Code Playgroud)

所以你要问钥匙的长度?您可以使用密钥的md5sum而不是直接使用它.

更多,根据我使用PyCrypto的经验,当输入相同时,IV用于混合加密输出,因此选择IV作为随机字符串,并将其用作加密输出的一部分,然后用它来解密消息.

这是我的实现,希望它对您有用:

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:] ))
Run Code Online (Sandbox Code Playgroud)

  • @Kjir,然后将长度为BLOCK_SIZE的值chr(BS)序列附加到原始数据. (2认同)
  • @Marcus 的 `pad` 函数被破坏了(至少在 Py3 中),用 `s[:-ord(s[len(s)-1:])]` 替换以使其能够跨版本工作。 (2认同)
  • @Torxed pad功能在CryptoUtil.Padding.pad()中使用pycryptodome(pycrypto后续) (2认同)
  • 为什么不将字符常量作为填充字符? (2认同)

mno*_*hic 139

这是我的实现,并为我提供了一些修复,并增强了密钥和密码短语与32字节和iv到16字节的对齐:

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:])]
Run Code Online (Sandbox Code Playgroud)

  • 我知道这已经有一段时间了,但我认为这种回应可能会引起一些混乱。此函数使用 32 字节(256 字节)的 block_size 来填充输入数据,但 AES 使用 128 位块大小。在 AES256 中,*key* 是 256 位,但不是块大小。 (14认同)
  • 换句话说,应该删除"self.bs"并替换为"AES.block_size" (12认同)
  • @mnothic - 愚蠢的问题:为什么`s[:-ord(s[len(s)-1:])]` 而不是`s[:-ord(s[-1])]`? (3认同)
  • 为什么要散列密钥?如果您期望这就像一个密码,那么您不应该使用SHA256。最好使用PyCrypto提供的密钥派生功能,例如PBKDF2。 (2认同)
  • @Chris - SHA256发出一个32字节的散列 - 一个完美大小的AES256密钥.一个关键的生成/推导被认为是随机/安全,就应该出在加密/解密代码的范围 - 哈希只是一个保证,关键是所选择的密码使用. (2认同)
  • @mnothic “_pad” 是普通类方法而“_unpad” 是“staticmethod” 的原因是什么?有什么不同? (2认同)
  • 在 _pad self.bs 中需要访问,而在 _unpad 中不需要 (2认同)
  • 请添加一个示例来展示如何使用此类。 (2认同)
  • 我必须对其进行修改,以便能够加密包含非 ASCII 字符的字符串,例如 áàâãéèêíïóôõöúçñÁÀаÉÈÍÏÓÔÕÖÚÇÑ。为此,我必须将 _pad() 中的 len(s) 替换为 **len(s.encode())** 以获得正确的填充。否则我会收到以下错误:“数据必须在 cbc 模式下填充到 16 字节边界”。 (2认同)

twe*_*ksp 11

让我来谈谈你关于"模式"的问题.AES256是一种分组密码.它将一个32字节的密钥和一个16字节的字符串作为输入,称为并输出一个块.我们在一种操作模式下使用AES 来加密.上述解决方案建议使用CBC,这是一个例子.另一种称为CTR,它更容易使用:

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)
Run Code Online (Sandbox Code Playgroud)

这通常被称为AES-CTR.我建议谨慎使用AES-CBC和PyCrypto.原因是它需要您指定填充方案,如给出的其他解决方案所示.一般来说,如果你对填充不是小心,那么就会有完全破坏加密的攻击!

现在,重要的是要注意密钥必须是一个随机的32字节字符串 ; 密码不够的.通常,密钥生成如下:

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

密钥也可以从密码派生:

# 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)
Run Code Online (Sandbox Code Playgroud)

上面的一些解决方案建议使用SHA256来获取密钥,但这通常被认为是不良的加密实践.查看维基百科,了解有关操作模式的更多信息.


Hoa*_*HUA 7

对于想要使用urlsafe_b64encode和urlsafe_b64decode的人,这里有适用于我的版本(花了一些时间来解决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:]))
Run Code Online (Sandbox Code Playgroud)


cen*_*ioz 7

感谢其他启发但对我不起作用的答案。

花了几个小时试图弄清楚它是如何工作后,我想出了下面的最新实施PyCryptodomex库(这是另一个故事,我如何设法设置它背后的代理,在Windows上,在virtualenv中..唷)

工作的你的实现,记得写下填充、编码、加密步骤(反之亦然)。您必须牢记订单来打包和拆包。

导入 base64
导入哈希库
从 Cryptodome.Cipher 导入 AES
从 Cryptodome.Random 导入 get_random_bytes

__key__ = hashlib.sha256(b'16-character key').digest()

定义加密(原始):
    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)
    密码 = AES.new(key= __key__, mode= AES.MODE_CFB,iv= iv)
    返回 base64.b64encode(iv + cipher.encrypt(raw))

def解密(加密):
    unpad = lambda s: s[:-ord(s[-1:])]

    enc = base64.b64decode(enc)
    iv = enc[:AES.block_size]
    密码 = AES.new(__key__, AES.MODE_CFB, iv)
    返回 unpad(base64.b64decode(cipher.decrypt(enc[AES.block_size:])).decode('utf8'))


nne*_*neo 6

您可以使用加密哈希函数(不是 Python的内置函数)(hash如SHA-1或SHA-256)从任意密码中获取密码.Python在其标准库中包含对两者的支持:

import hashlib

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

您只需使用[:16]或截断加密哈希值[:24],它将保持其安全性达到您指定的长度.

  • 您不应该使用SHA系列哈希函数从密码生成密钥 - 请参阅[Coda Hale关于该主题的文章](http://codahale.com/how-to-safely-store-a-password/).考虑使用[scrypt](https://pypi.python.org/pypi/scrypt/)这样的真实[密钥派生函数](https://en.wikipedia.org/wiki/Key_derivation_function).(Coda Hale的文章是在scrypt出版之前写的.) (11认同)
  • 对于未来的读者,如果您希望从密码短语中获取密钥,请查找PBKDF2.它在python中很容易使用(https://pypi.python.org/pypi/pbkdf2).但是,如果您正在寻找哈希密码,那么bcrypt是一个更好的选择. (6认同)

MIk*_*kee 6

对此的另一种看法(很大程度上来自上述解决方案)但是

  • 使用 null 进行填充
  • 不使用 lambda(从来不是粉丝)
  • 用 python 2.7 和 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')
    
    Run Code Online (Sandbox Code Playgroud)


sco*_*ski 5

为了他人的利益,这里是我的解密实现,我将@Cyril和@Marcus的答案结合起来.这假设这是通过HTTP请求引入的,其中引用了encryptedText和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()
Run Code Online (Sandbox Code Playgroud)


Sma*_*pha 5

我已经使用了这两个CryptoPyCryptodomex,它的速度非常快......

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)
Run Code Online (Sandbox Code Playgroud)