Шифрование и расшифровка с помощью PyCrypto AES 256
Я пытаюсь построить две функции с помощью PyCrypto, которые принимают два параметра: сообщение и ключ, а затем шифруют/расшифровывают сообщение.
Я нашел несколько ссылок в интернете, чтобы помочь мне, но каждый из них имеет недостатки:
этот в codekoala использует ОС.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))
9 ответов:
вот моя реализация и работает для меня с некоторыми исправлениями и улучшает выравнивание ключа и секретной фразы с 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:])]
вам могут понадобиться следующие две функции для pad(когда шифрование) и unpad (когда расшифровка), когда длина ввода не кратна 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:])]
Итак, вы спрашиваете длину ключа? Вы можете использовать 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:] ))
вы можете получить пароль из произвольного пароля с помощью криптографической хэш-функции (не в 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]
и он сохранит свою безопасность до указанной длины.
для тех, кто хотел бы использовать 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-запрос с 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()
позвольте мне ответить на ваш вопрос о "режимах."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. Причина в том, что он требует от вас указать схема, как показано на примере других приведенных решений. В общем, если ты не очень осторожно с прокладкой,здесь ударов что полностью сломать шифрование!
Теперь, важно отметить, что ключ должен быть случайная, 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 для получения ключа, но это обычно считается плохая криптографическая практика. Проверьте Википедия для получения дополнительной информации о режимах работы.
немного поздно, но я думаю, что это будет очень полезно. Никто не упоминает о схеме использования, такой как PKCS#7 padding. Вы можете использовать его вместо предыдущих функций для pad(когда шифрование) и unpad (когда расшифровка).я приведу полный исходный код.
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())
другое взятие на это (сильно выведенное из решений выше) но
- использует null для заполнения
- не использует лямбда (никогда не был поклонником)
протестировано с 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 + '' * (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')
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:])