Merge "Replace pycrypto with cryptography"
This commit is contained in:
commit
e1cd9a47e1
|
@ -17,8 +17,8 @@
|
|||
Utilities for memcache encryption and integrity check.
|
||||
|
||||
Data should be serialized before entering these functions. Encryption
|
||||
has a dependency on the pycrypto. If pycrypto is not available,
|
||||
CryptoUnavailableError will be raised.
|
||||
has a dependency on the cryptography module. If cryptography is not
|
||||
available, CryptoUnavailableError will be raised.
|
||||
|
||||
This module will not be called unless signing or encryption is enabled
|
||||
in the config. It will always validate signatures, and will decrypt
|
||||
|
@ -33,17 +33,19 @@ import hashlib
|
|||
import hmac
|
||||
import math
|
||||
import os
|
||||
import six
|
||||
|
||||
from oslo_utils import secretutils
|
||||
|
||||
from keystonemiddleware.i18n import _
|
||||
from oslo_utils import secretutils
|
||||
|
||||
# make sure pycrypto is available
|
||||
try:
|
||||
from Crypto.Cipher import AES
|
||||
from cryptography.hazmat import backends as crypto_backends
|
||||
from cryptography.hazmat.primitives import ciphers
|
||||
from cryptography.hazmat.primitives.ciphers import algorithms
|
||||
from cryptography.hazmat.primitives.ciphers import modes
|
||||
from cryptography.hazmat.primitives import padding
|
||||
except ImportError:
|
||||
AES = None
|
||||
ciphers = None
|
||||
|
||||
|
||||
HASH_FUNCTION = hashlib.sha384
|
||||
DIGEST_LENGTH = HASH_FUNCTION().digest_size
|
||||
|
@ -74,10 +76,10 @@ class CryptoUnavailableError(Exception):
|
|||
|
||||
|
||||
def assert_crypto_availability(f):
|
||||
"""Ensure Crypto module is available."""
|
||||
"""Ensure cryptography module is available."""
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args, **kwds):
|
||||
if AES is None:
|
||||
if ciphers is None:
|
||||
raise CryptoUnavailableError()
|
||||
return f(*args, **kwds)
|
||||
return wrapper
|
||||
|
@ -116,24 +118,39 @@ def encrypt_data(key, data):
|
|||
Padding is n bytes of the value n, where 1 <= n <= blocksize.
|
||||
"""
|
||||
iv = os.urandom(16)
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
padding = 16 - len(data) % 16
|
||||
return iv + cipher.encrypt(data + six.int2byte(padding) * padding)
|
||||
cipher = ciphers.Cipher(
|
||||
algorithms.AES(key),
|
||||
modes.CBC(iv),
|
||||
backend=crypto_backends.default_backend())
|
||||
|
||||
# AES algorithm uses block size of 16 bytes = 128 bits, defined in
|
||||
# algorithms.AES.block_size. Previously, we manually padded this using
|
||||
# six.int2byte(padding) * padding. Using ``cryptography``, we will
|
||||
# analogously use hazmat.primitives.padding to pad it to
|
||||
# the 128-bit block size.
|
||||
padder = padding.PKCS7(algorithms.AES.block_size).padder()
|
||||
padded_data = padder.update(data) + padder.finalize()
|
||||
encryptor = cipher.encryptor()
|
||||
return iv + encryptor.update(padded_data) + encryptor.finalize()
|
||||
|
||||
|
||||
@assert_crypto_availability
|
||||
def decrypt_data(key, data):
|
||||
"""Decrypt the data with the given secret key."""
|
||||
iv = data[:16]
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
cipher = ciphers.Cipher(
|
||||
algorithms.AES(key),
|
||||
modes.CBC(iv),
|
||||
backend=crypto_backends.default_backend())
|
||||
try:
|
||||
result = cipher.decrypt(data[16:])
|
||||
decryptor = cipher.decryptor()
|
||||
result = decryptor.update(data[16:]) + decryptor.finalize()
|
||||
except Exception:
|
||||
raise DecryptError(_('Encrypted data appears to be corrupted.'))
|
||||
|
||||
# Strip the last n padding bytes where n is the last value in
|
||||
# the plaintext
|
||||
return result[:-1 * six.byte2int([result[-1]])]
|
||||
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
|
||||
return unpadder.update(result) + unpadder.finalize()
|
||||
|
||||
|
||||
def protect_data(keys, data):
|
||||
|
|
|
@ -67,10 +67,10 @@ class MemcacheCryptPositiveTests(utils.BaseTestCase):
|
|||
keys, protected[:-1])
|
||||
self.assertIsNone(memcache_crypt.unprotect_data(keys, None))
|
||||
|
||||
def test_no_pycrypt(self):
|
||||
aes = memcache_crypt.AES
|
||||
memcache_crypt.AES = None
|
||||
def test_no_cryptography(self):
|
||||
aes = memcache_crypt.ciphers
|
||||
memcache_crypt.ciphers = None
|
||||
self.assertRaises(memcache_crypt.CryptoUnavailableError,
|
||||
memcache_crypt.encrypt_data, 'token', 'secret',
|
||||
'data')
|
||||
memcache_crypt.AES = aes
|
||||
memcache_crypt.ciphers = aes
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
fixes:
|
||||
- |
|
||||
[`bug 1677308 <https://bugs.launchpad.net/keystonemiddleware/+bug/1677308>`_]
|
||||
Removes ``pycrypto`` dependency as the library is unmaintained, and
|
||||
replaces it with the ``cryptography`` library.
|
||||
upgrade:
|
||||
- |
|
||||
[`bug 1677308 <https://bugs.launchpad.net/keystonemiddleware/+bug/1677308>`_]
|
||||
There is no upgrade impact when switching from ``pycrypto`` to
|
||||
``cryptography``. All data will be encrypted and decrypted using identical
|
||||
blocksize, padding, algorithm (AES) and mode (CBC). Data previously
|
||||
encrypted using ``pycrypto`` can be decrypted using both ``pycrypto`` and
|
||||
``cryptography``. The same is true of data encrypted using
|
||||
``cryptography``.
|
|
@ -6,10 +6,10 @@ hacking<0.11,>=0.10.0
|
|||
flake8-docstrings==0.2.1.post1 # MIT
|
||||
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
cryptography>=1.6 # BSD/Apache-2.0
|
||||
docutils>=0.11 # OSI-Approved Open Source, Public Domain
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
mock>=2.0 # BSD
|
||||
pycrypto>=2.6 # Public Domain
|
||||
oslosphinx>=4.7.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
reno>=1.8.0 # Apache-2.0
|
||||
|
|
Loading…
Reference in New Issue