2012-09-21 199 views
112

我试图建立两个函数使用PyCrypto接受两个参数:消息和密钥,然后加密/解密消息。加密和解密使用PyCrypto AES 256

我在网上找到了几个环节,以帮助我,但他们每个人都有缺点:

This one at codekoala使用os.urandom,这是由PyCrypto气馁。

此外,我给函数的关键不能保证具有预期的确切长度。我该怎么做才能做到这一点?

此外,还有几种模式,推荐哪一种?我不知道该用什么:/

最后,到底什么是IV?我可以提供不同的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)) 
+10

[os.urandom](http://docs.python.org/3/library/os.html)被_encouraged_上的[PyCrypto](https://www.dlitz.net/software/pycrypto/)网站。它使用Microsoft的[CryptGenRandom](http://msdn.microsoft.com/en-us/library/windows/desktop/aa379942(V = vs.85)的.aspx)函数,它是一个[CSPRNG](HTTP:// en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) –

+4

或'的/ dev/urandom'在Unix –

+1

只是为了澄清,在该示例** **密码是其可以是128,192,或256位(16,24的_key_ ,或32个字节) – Mark

回答

84

这里是我的实现,并与一些修复工作对我来说并增强了与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 = 32 
     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)) 

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

我知道这已经持续了一段时间,但我认为这种回应可能会传播一些混乱。此函数使用32字节(256字节)的block_size填充输入数据,但AES使用128位块大小。在AES256中*键*是256位,但不是块大小。 – Tannin

+4

换句话说,“self.bs”应该被删除并替换为“AES.block_size” – Alexis

+0

你为什么要哈希键?如果你期待这是一个密码,那么你不应该使用SHA256;最好使用密钥派生函数,比如PyCrypto提供的PBKDF2。 – tweaksp

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 

可以截断的加密哈希值只是通过使用[:16][:24],它会保留其安全性达到您所指定的长度。

+10

你不应该使用SHA-家庭哈希函数从密码生成密钥 - 参见[科达硬朗的在话题作文(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出版之前撰写的。) –

+5

对于未来的读者,如果您希望从密码中派生密钥,请查找PBKDF2。在python中使用相当简单(https://pypi.python.org/pypi/pbkdf2)。但是,如果你想要散列密码,bcrypt是一个更好的选择。 –

130

当输入的长度不是BLOCK_SIZE的倍数时,您可能需要以下两个函数来填充(何时进行加密)和unpad(何时进行解密)。

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

所以你问的关键长度?您可以使用密钥的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:])) 
+1

如果您的输入完全是BLOCK_SIZE的倍数,会发生什么情况?我认为unpad函数会产生一些困惑...... – Kjir

+1

@Kjir,则长度为BLOCK_SIZE的值序列chr(BS)将被附加到原始数据。 – Marcus

+0

你说得对,现在'pad'和'unpad'函数更清晰了,谢谢! – Kjir

5

为他人谋取利益,这里是我的解密实现,我结合@Cyril和@Marcus的答案得。这假定这是通过HTTP Request与加密文本引用和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() 
6

对于有人谁愿意使用urlsafe_b64encode和urlsafe_b64decode,这里的版本that're工作对我来说(花一些时间与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:])) 
+0

这代码将打破,如果'BS = 32' –

0
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:]) 
+6

请提供不仅代码,但也解释你在做什么,为什么这是更好/什么是对现有答案的区别。 –

2

这有点晚,但我认为这会非常有帮助。没有人提到像PKCS#7填充这样的使用方案。你可以使用它代替之前的函数来填充(何时进行加密)和unpad(何时解密).i将在下面提供完整的源代码。

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

+0

我不知道谁低估了答案,但我很想知道为什么。也许这种方法不安全?解释会很棒。 –

+0

至少我不是那个倒下来的人:) – xXxpRoGrAmmErxXx

+1

@CyrilN。这个答案表明用SHA-256的单个调用来散列密码就足够了。事实并非如此。你真的应该使用PBKDF2或类似的密码从密码派生使用大量的迭代计数。 –

2

另取这个(在很大程度上借鉴的解决方案衍生以上),但

  • 用于填充使用空
  • 不使用拉姆达(从不一直是粉丝)

    #!/usr/bin/env python 
    
    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) 
         iv = Random.new().read(AES.block_size) 
         cipher = AES.new(self.key, 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, AES.MODE_CBC, iv) 
         return re.sub(b'\x00*$', b'', cipher.decrypt(enc[16:])).decode('utf-8') 
    
+0

如果输入字节[]具有拖尾空值,那么这将不起作用,因为在decrypt()函数中,您将填充空白填充任何拖尾的空值。 –

+0

是的,正如我在上面所述,这个逻辑电路填充了空值。如果您想编码/解码的项目可能会有空值,那么最好在这里使用其他解决方案之一 – MIkee

5

让我解决你的问题 “的模式。” 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) 

这通常被称为AES-CTR。 我建议在使用AES-CBC和PyCrypto时慎用。原因是它要求您指定填充方案,如其他解决方案所示。一般来说,如果你不是细心的填充,还有attacks那个完全破解加密!

现在,重要的是要注意,密钥必须是随机的32字节字符串;密码不需要就够了。通常情况下,关键是像这样产生的:

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

可将钥匙从密码,也得出:

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

一些解决方案上面的建议使用SHA256推导的关键,但这是一般认为是bad cryptographic practice。 查看wikipedia了解更多关于操作模式的信息。