使用PyCrypto AES 256进行encryption和解密
我试图build立两个函数使用PyCrypto接受两个参数:消息和密钥,然后encryption/解密消息。
我在网上find了几个链接来帮助我,但是他们每个人都有缺陷:
这个在codekoala上使用os.urandom,这是由PyCrypto不鼓励。
而且,我给这个函数的关键并不能保证有准确的长度。 我能做些什么来做到这一点?
还有几种模式,推荐哪一种? 我不知道该用什么:/
最后,IV究竟是什么? 我可以提供一个不同的四encryption和解密,或者这将返回一个不同的结果?
以下是我迄今为止所做的:
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))
这里是我的实现,并为我工作一些修复,并增强了32字节和iv到16字节的密钥和秘密短语的alignment方式:
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:])]
当input的长度不是BLOCK_SIZE的倍数时,可能需要以下两个函数来填充(何时进行encryption)和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的一些经验,当input相同时,IV被用来混合encryption的输出,所以IV被选作一个随机的string,并将其用作encryption输出的一部分,然后用它来解密消息。
这是我的实现,希望它对你有用:
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:] ))
您可以使用密码散列函数( 不是 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]
即可截断encryption散列值,并将保持其安全性,直到您指定的长度。
对于想要使用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:]))
为了别人的利益,这里是我通过结合@Cyril和@Marcus的答案得到的解密实现。 这假设这通过HTTP请求与encryption文本引用和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()
让我来谈谈你关于“模式”的问题。 AES256是一种分组密码 。 它需要input一个32字节的密钥和一个16字节的string,称为块并输出一个块。 我们使用AES进行encryption。 上面的解决schemebuild议使用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。 我build议在使用AES-CBC和PyCrypto时慎用 。 原因是它要求你指定填充scheme ,如其他解决scheme所示。 一般来说,如果你对填充不是很小心的话, 那么攻击就完全破坏了encryption!
现在需要注意的是,密钥必须是随机的,32字节的string 。 一个密码是不够的。 通常情况下,密钥是这样生成的:
# 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)
上面的一些解决schemebuild议使用SHA256来推导密钥,但这通常被认为是不好的密码实践 。 查看维基百科了解更多关于操作模式。
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:])
另一个承担这个(从上面的解决scheme大量派生),但
- 使用null填充
-
不使用lambda(从来不是粉丝)
#!/usr/bin/env python import base64, re from Crypto.Cipher import AES from Crypto import Random class AESCipher: 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: return '' 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 ) ) def decrypt( self, enc ): if enc is None or len(enc) == 0: return '' enc = base64.b64decode(enc) iv = enc[:16] cipher = AES.new(self.key, AES.MODE_CBC, iv ) return re.sub('\0*$','', cipher.decrypt( enc[16:])) aes = AESCipher( '1234567890123456'[:16], 32) encryp_msg = aes.encrypt('ppppppppppppppppppppppppppppppppppppppppppppppppppppppp') msg = aes.decrypt( encryp_msg ) print "'{}'".format(msg)
这有点晚,但我认为这将是非常有帮助的。 没有人提到像PKCS#7填充的使用scheme。 你可以使用它来替代之前的函数来填充(何时做encryption)和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())