Replace pycrypto with cryptography

Pycrypto is no longer maintained, and heat currently uses Pycrypto.
This patch rewrites functions using Pycrypto and replaces them with
cryptography functions.

See [1], [2] for reference.


Change-Id: I27e7208e19726c5325bad2874e43f5d841779008
Co-Authored-By: Omar Tleimat <>
Jaewoo Park 6 years ago committed by Zane Bitter
parent ec40f1301d
commit 1397100f9d

@ -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.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:]) +
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.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():

@ -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