From 7c16877195e90ccf0fcdba79273c633f0c591230 Mon Sep 17 00:00:00 2001 From: Matthew Thode Date: Wed, 13 Jun 2018 11:33:00 -0500 Subject: [PATCH] switch to cryptography Fernet could not be used as it is not a streaming cipher. CFB mode does not require padding, so removed padding code. Depends-On: https://review.openstack.org/c/575199/ Change-Id: I14f58d9d473c20ce7863baa0b2adadbfda268b7a --- doc/README.rst | 4 +-- freezer/engine/rsyncv2/rsyncv2.py | 1 - freezer/utils/crypt.py | 53 ++++++++++--------------------- lower-constraints.txt | 3 +- requirements.txt | 2 +- 5 files changed, 21 insertions(+), 42 deletions(-) diff --git a/doc/README.rst b/doc/README.rst index 97dcfaf0..8fe528d9 100644 --- a/doc/README.rst +++ b/doc/README.rst @@ -124,7 +124,7 @@ Linux Requirements - libffi-dev - libssl-dev - python-dev -- pycrypto +- cryptography - At least 128 MB of memory reserved for Freezer Windows Requirements @@ -587,7 +587,7 @@ Freezer architectural components are the following: - freezer client running on the node where the backups and restores are to be executed Freezer uses GNU Tar or Rsync algorithm under the hood to execute incremental backup and -restore. When a key is provided, it uses OpenSSL or pycrypto module (OpenSSL compatible) +restore. When a key is provided, it uses OpenSSL or cryptography module (OpenSSL compatible) to encrypt data. (AES-256-CFB) ============= diff --git a/freezer/engine/rsyncv2/rsyncv2.py b/freezer/engine/rsyncv2/rsyncv2.py index 0d075762..20fb158c 100644 --- a/freezer/engine/rsyncv2/rsyncv2.py +++ b/freezer/engine/rsyncv2/rsyncv2.py @@ -145,7 +145,6 @@ class Rsyncv2Engine(engine.BackupEngine): flushed_data += compressor.flush() if flushed_data and cipher: flushed_data = cipher.encrypt(flushed_data) - flushed_data += cipher.flush() return flushed_data diff --git a/freezer/utils/crypt.py b/freezer/utils/crypt.py index 9b4546a1..28396447 100644 --- a/freezer/utils/crypt.py +++ b/freezer/utils/crypt.py @@ -14,12 +14,15 @@ import hashlib -from Crypto.Cipher import AES -from Crypto import Random +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers import modes +from os import urandom SALT_HEADER = 'Salted__' AES256_KEY_LENGTH = 32 -BS = AES.block_size +BS = 16 # static 16 bytes, 128 bits for AES class AESCipher(object): @@ -56,40 +59,21 @@ class AESEncrypt(AESCipher): def __init__(self, pass_file): super(AESEncrypt, self).__init__(pass_file) - self._salt = Random.new().read(BS - len(SALT_HEADER)) + self._salt = urandom(BS - len(SALT_HEADER)) key, iv = self._derive_key_and_iv(self._password, self._salt, AES256_KEY_LENGTH, BS) - self.cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=BS * 8) - self.remain = None + self.cipher = Cipher(algorithms.AES(key), + modes.CFB(iv), + backend=default_backend()) def generate_header(self): return SALT_HEADER + self._salt def encrypt(self, data): - remain = self.remain - if remain: - data = remain + data - remain = None - - extra_bytes = len(data) % BS - if extra_bytes: - remain = data[-extra_bytes:] - data = data[:-extra_bytes] - - self.remain = remain - return self.cipher.encrypt(data) - - def flush(self): - def pad(s): - return s + (BS - len(s) % BS) * chr(BS - len(s) % BS) - - buff = self.remain - if buff: - return self.cipher.encrypt(pad(buff)) - - return b'' + encryptor = self.cipher.encryptor() + return encryptor.update(data) + encryptor.finalize() class AESDecrypt(AESCipher): @@ -105,17 +89,14 @@ class AESDecrypt(AESCipher): self._salt, AES256_KEY_LENGTH, BS) - self.cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=BS * 8) + self.cipher = Cipher(algorithms.AES(key), + modes.CFB(iv), + backend=default_backend()) @staticmethod def _prepare_salt(salt): return salt[len(SALT_HEADER):] def decrypt(self, data): - # def unpad(s): - # return s[0:-ord(s[-1])] - # - # if last_block: - # return unpad(self.cipher.decrypt(data)) - # else: - return self.cipher.decrypt(data) + decryptor = self.cipher.decryptor() + return decryptor.update(data) + decryptor.finalize() diff --git a/lower-constraints.txt b/lower-constraints.txt index 89912f1d..699c2f60 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -12,7 +12,7 @@ chardet==3.0.4 cliff==2.11.0 cmd2==0.8.1 coverage==4.0 -cryptography==2.1.4 +cryptography==2.1 ddt==1.0.1 debtcollector==1.19.0 decorator==4.2.1 @@ -74,7 +74,6 @@ prettytable==0.7.2 psutil==3.2.2 pyasn1==0.4.2 pycparser==2.18 -pycrypto==2.6 pyflakes==1.0.0 Pygments==2.2.0 pyinotify==0.9.6 diff --git a/requirements.txt b/requirements.txt index dc05daf6..31128986 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ keystoneauth1>=3.4.0 # Apache-2.0 os-brick>=2.2.0 # Apache-2.0 oslo.service!=1.28.1,>=1.24.0 # Apache-2.0 -pycrypto>=2.6 # Public Domain +cryptography>=2.1 # Apache-2.0 PyMySQL>=0.7.6 # MIT License pymongo!=3.1,>=3.0.2 # Apache-2.0 paramiko>=2.0.0 # LGPLv2.1+