diff --git a/heat/common/crypt.py b/heat/common/crypt.py index ac9cdcf1b1..aef99ae401 100644 --- a/heat/common/crypt.py +++ b/heat/common/crypt.py @@ -14,12 +14,15 @@ import base64 import sys -from Crypto.Cipher import AES from cryptography import fernet +from cryptography.hazmat import backends +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers import modes +from cryptography.hazmat.primitives import padding from oslo_config import cfg from oslo_serialization import jsonutils from oslo_utils import encodeutils -from oslo_utils import importutils from heat.common import exception from heat.common.i18n import _ @@ -41,16 +44,16 @@ class SymmetricCrypto(object): This class creates a Symmetric Key Crypto object that can be used to decrypt arbitrary data. - Note: This is moved here from oslo-incubator for backward - compatibility. Once we've db migration script available to - re-rencrypt using new encryption method as part of upgrade, - this can be removed. + Note: This is a reimplementation of the decryption algorithm from + oslo-incubator, and is provided for backward compatibility. Once we have a + db migration script available to re-encrypt using new encryption method as + part of upgrade, this can be removed. :param enctype: Encryption Cipher name (default: AES) """ def __init__(self, enctype='AES'): - self.cipher = importutils.import_module('Crypto.Cipher.' + enctype) + self.algo = algorithms.AES def decrypt(self, key, msg, b64decode=True): """Decrypts the provided ciphertext. @@ -64,15 +67,24 @@ class SymmetricCrypto(object): :returns plain: the plaintext message, after padding is removed. """ + key = str.encode(get_valid_encryption_key(key)) if b64decode: msg = base64.b64decode(msg) - iv = msg[:self.cipher.block_size] - cipher = self.cipher.new(key, self.cipher.MODE_CBC, iv) - - padded = cipher.decrypt(msg[self.cipher.block_size:]) - l = ord(padded[-1:]) + 1 - plain = padded[:-l] - return plain + algo = self.algo(key) + block_size_bytes = algo.block_size // 8 + iv = msg[:block_size_bytes] + backend = backends.default_backend() + cipher = Cipher(algo, modes.CBC(iv), backend=backend) + decryptor = cipher.decryptor() + padded = (decryptor.update(msg[block_size_bytes:]) + + decryptor.finalize()) + unpadder = padding.ANSIX923(algo.block_size).unpadder() + plain = unpadder.update(padded) + unpadder.finalize() + # The original padding algorithm was a slight variation on ANSI X.923, + # where the size of the padding did not include the byte that tells + # you the size of the padding. Therefore, we need to remove one extra + # byte (which will be 0x00) when unpadding. + return plain[:-1] def encrypt(value, encryption_key=None): @@ -155,12 +167,15 @@ def heat_decrypt(value, encryption_key=None): function must still exist. So whilst it may seem that this function is not referenced, it will be referenced from the database. """ - encryption_key = get_valid_encryption_key(encryption_key) + encryption_key = str.encode(get_valid_encryption_key(encryption_key)) auth = base64.b64decode(value) - iv = auth[:AES.block_size] - cipher = AES.new(encryption_key, AES.MODE_CFB, iv) - res = cipher.decrypt(auth[AES.block_size:]) - return res + AES = algorithms.AES(encryption_key) + block_size_bytes = AES.block_size // 8 + iv = auth[:block_size_bytes] + backend = backends.default_backend() + cipher = Cipher(AES, modes.CFB(iv), backend=backend) + decryptor = cipher.decryptor() + return decryptor.update(auth[block_size_bytes:]) + decryptor.finalize() def list_opts(): diff --git a/requirements.txt b/requirements.txt index 169d1e4432..a33a0010ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,6 @@ oslo.utils>=3.28.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 oslo.versionedobjects>=1.28.0 # Apache-2.0 PasteDeploy>=1.5.0 # MIT -pycrypto>=2.6 # Public Domain aodhclient>=0.9.0 # Apache-2.0 python-barbicanclient!=4.5.0,!=4.5.1,>=4.0.0 # Apache-2.0 python-ceilometerclient>=2.5.0 # Apache-2.0