我正在尝试使用PyCrypto构建两个函数,接受两个参数:消息和密钥,然后加密/解密消息。
我在网上找到了几个链接来帮助我,但它们每一个都有缺陷:
codekoala的这个使用 os.urandom,但是PyCrypto不鼓励这样做。
此外,我给函数的密钥的长度不能保证精确。我该怎么办才能让它符合要求?
还有几种模式可用,哪一种推荐使用?我不知道该用什么 :/
最后,IV是什么? 我可以为加密和解密提供不同的IV吗,或者这会导致结果不同?
我正在尝试使用PyCrypto构建两个函数,接受两个参数:消息和密钥,然后加密/解密消息。
我在网上找到了几个链接来帮助我,但它们每一个都有缺陷:
codekoala的这个使用 os.urandom,但是PyCrypto不鼓励这样做。
此外,我给函数的密钥的长度不能保证精确。我该怎么办才能让它符合要求?
还有几种模式可用,哪一种推荐使用?我不知道该用什么 :/
最后,IV是什么? 我可以为加密和解密提供不同的IV吗,或者这会导致结果不同?
请查看mnothic的最初回答。
UTF-8编码兼容:
def _pad(self, s):
s = s.encode()
res = s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs).encode()
return res
对于需要加密和解密拉丁字符和特殊值(例如中文)的用户,这里是对@MIkee代码进行修改以完成此任务。
请记住,仅使用UTF-8无法处理此类型的编码。
import base64, re
from Crypto.Cipher import AES
from Crypto import Random
from django.conf import settings
import codecs
# Make utf8mb4 recognizable.
codecs.register(lambda name: codecs.lookup('utf8') if name == 'utf8mb4' else None)
class AESCipher:
def __init__(self, key, blk_sz):
self.key = key
self.blk_sz = blk_sz
def encrypt( self, raw ):
# raw is the main value
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('utf8mb4')
# Initialization vector to avoid same encrypt for same strings.
iv = Random.new().read( AES.block_size )
cipher = AES.new( self.key.encode('utf8mb4'), AES.MODE_CFB, iv )
return base64.b64encode( iv + cipher.encrypt( raw ) ).decode('utf8mb4')
def decrypt( self, enc ):
# enc is the encrypted value
if enc is None or len(enc) == 0:
raise NameError("No value given to decrypt")
enc = base64.b64decode(enc)
iv = enc[:16]
# AES.MODE_CFB that allows bigger length or Latin values
cipher = AES.new(self.key.encode('utf8mb4'), AES.MODE_CFB, iv )
return re.sub(b'\x00*$', b'', cipher.decrypt( enc[16:])).decode('utf8mb4')
使用方法:
>>> from django.conf import settings
>>> from aesencryption import AESCipher
>>>
>>> aes = AESCipher(settings.SECRET_KEY[:16], 32)
>>>
>>> value = aes.encrypt('漢字')
>>>
>>> value
'hnuRwBjwAHDp5X0DmMF3lWzbjR0r81WlW9MRrWukgQwTL0ZI88oQaWvMfBM+W87w9JtSTw=='
>>> dec_value = aes.decrypt(value)
>>> dec_value
'漢字'
>>>
ã, á, à, â, ã, ç
等等。PyCrypto已经有些老旧了。
现在cryptography提供了更好的支持。
这里是另一个实现。请注意,它返回字节(bytes),你需要使用base64将它们转换为字符串以进行传输。
import os
import hashlib
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
_BLOCK_SIZE = 16
class AesStringCipher:
def __init__(self, key):
self._key = hashlib.sha256(key.encode()).digest()
def encrypt_str(self, raw:str) -> bytes:
iv = os.urandom(_BLOCK_SIZE)
cipher = Cipher(algorithms.AES(self._key), modes.CBC(iv), default_backend())
encryptor = cipher.encryptor()
raw = _pad(raw)
return iv + encryptor.update(raw.encode('utf-8')) + encryptor.finalize()
def decrypt_str(self, enc:bytes) -> str:
iv = enc[:_BLOCK_SIZE]
enc = enc[_BLOCK_SIZE:]
cipher = Cipher(algorithms.AES(self._key), modes.CBC(iv), default_backend())
decryptor = cipher.decryptor()
raw = decryptor.update(enc) + decryptor.finalize()
raw = raw.decode('utf-8')
return _unpad(raw)
def _pad(s:str) -> str:
padding = (_BLOCK_SIZE - (len(s) % _BLOCK_SIZE))
return s + padding * chr(padding)
def _unpad(s:str) -> str:
return s[:-ord(s[len(s)-1:])]
if __name__ == '__main__':
cipher = AesStringCipher('my secret password')
secret_msg = 'this is a super secret msg ...'
enc_msg = cipher.encrypt_str(secret_msg)
dec_msg = cipher.decrypt_str(enc_msg)
assert secret_msg == dec_msg
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:])
你可以使用新的django-mirage-field包。
_unpad
改为实例方法,因为在decrypt
中它是通过self.
前缀而不是AESCipher
前缀调用的。我所做的第二件事是改变填充操作顺序,以适应使用字符编码后结果超过一个字节的文本,从而移动填充位置。所以我所做的就是将填充放在编码之后。最后,我将填充和��填充逻辑更改为能够处理字节而不是字符串,并且还考虑了极端情况,即原始文本不需要填充的情况下,之前的逻辑会失败。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):
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(self._pad(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) * bytes(0x01)
def _unpad(self, s):
for i in range(1, len(s) - 1):
if s[-i] != 0:
break
i -= 1
return s[:-i] if i > 0 else s
/dev/random
或/dev/urandom
来获取随机数据。 - Joel Vroom