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.

[1] http://lists.openstack.org/pipermail/openstack-dev/2017-March/113568.html
[2] http://paste.openstack.org/show/617715/

Change-Id: I27e7208e19726c5325bad2874e43f5d841779008
Co-Authored-By: Omar Tleimat <otleimat@ucla.edu>
This commit is contained in:
Jaewoo Park 2017-04-07 12:33:47 -07:00 committed by Zane Bitter
parent ec40f1301d
commit 1397100f9d
2 changed files with 34 additions and 20 deletions

View File

@ -14,12 +14,15 @@
import base64 import base64
import sys import sys
from Crypto.Cipher import AES
from cryptography import fernet 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_config import cfg
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import encodeutils from oslo_utils import encodeutils
from oslo_utils import importutils
from heat.common import exception from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
@ -41,16 +44,16 @@ class SymmetricCrypto(object):
This class creates a Symmetric Key Crypto object that can be used This class creates a Symmetric Key Crypto object that can be used
to decrypt arbitrary data. to decrypt arbitrary data.
Note: This is moved here from oslo-incubator for backward Note: This is a reimplementation of the decryption algorithm from
compatibility. Once we've db migration script available to oslo-incubator, and is provided for backward compatibility. Once we have a
re-rencrypt using new encryption method as part of upgrade, db migration script available to re-encrypt using new encryption method as
this can be removed. part of upgrade, this can be removed.
:param enctype: Encryption Cipher name (default: AES) :param enctype: Encryption Cipher name (default: AES)
""" """
def __init__(self, enctype='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): def decrypt(self, key, msg, b64decode=True):
"""Decrypts the provided ciphertext. """Decrypts the provided ciphertext.
@ -64,15 +67,24 @@ class SymmetricCrypto(object):
:returns plain: the plaintext message, after padding is removed. :returns plain: the plaintext message, after padding is removed.
""" """
key = str.encode(get_valid_encryption_key(key))
if b64decode: if b64decode:
msg = base64.b64decode(msg) msg = base64.b64decode(msg)
iv = msg[:self.cipher.block_size] algo = self.algo(key)
cipher = self.cipher.new(key, self.cipher.MODE_CBC, iv) block_size_bytes = algo.block_size // 8
iv = msg[:block_size_bytes]
padded = cipher.decrypt(msg[self.cipher.block_size:]) backend = backends.default_backend()
l = ord(padded[-1:]) + 1 cipher = Cipher(algo, modes.CBC(iv), backend=backend)
plain = padded[:-l] decryptor = cipher.decryptor()
return plain 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): 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 function must still exist. So whilst it may seem that this function
is not referenced, it will be referenced from the database. 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) auth = base64.b64decode(value)
iv = auth[:AES.block_size] AES = algorithms.AES(encryption_key)
cipher = AES.new(encryption_key, AES.MODE_CFB, iv) block_size_bytes = AES.block_size // 8
res = cipher.decrypt(auth[AES.block_size:]) iv = auth[:block_size_bytes]
return res 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(): def list_opts():

View File

@ -30,7 +30,6 @@ oslo.utils>=3.28.0 # Apache-2.0
osprofiler>=1.4.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0
oslo.versionedobjects>=1.28.0 # Apache-2.0 oslo.versionedobjects>=1.28.0 # Apache-2.0
PasteDeploy>=1.5.0 # MIT PasteDeploy>=1.5.0 # MIT
pycrypto>=2.6 # Public Domain
aodhclient>=0.9.0 # Apache-2.0 aodhclient>=0.9.0 # Apache-2.0
python-barbicanclient!=4.5.0,!=4.5.1,>=4.0.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 python-ceilometerclient>=2.5.0 # Apache-2.0