Merge "Replace pycrypto with cryptography"
This commit is contained in:
commit
e1cd9a47e1
|
@ -17,8 +17,8 @@
|
||||||
Utilities for memcache encryption and integrity check.
|
Utilities for memcache encryption and integrity check.
|
||||||
|
|
||||||
Data should be serialized before entering these functions. Encryption
|
Data should be serialized before entering these functions. Encryption
|
||||||
has a dependency on the pycrypto. If pycrypto is not available,
|
has a dependency on the cryptography module. If cryptography is not
|
||||||
CryptoUnavailableError will be raised.
|
available, CryptoUnavailableError will be raised.
|
||||||
|
|
||||||
This module will not be called unless signing or encryption is enabled
|
This module will not be called unless signing or encryption is enabled
|
||||||
in the config. It will always validate signatures, and will decrypt
|
in the config. It will always validate signatures, and will decrypt
|
||||||
|
@ -33,17 +33,19 @@ import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_utils import secretutils
|
|
||||||
|
|
||||||
from keystonemiddleware.i18n import _
|
from keystonemiddleware.i18n import _
|
||||||
|
from oslo_utils import secretutils
|
||||||
|
|
||||||
# make sure pycrypto is available
|
|
||||||
try:
|
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:
|
except ImportError:
|
||||||
AES = None
|
ciphers = None
|
||||||
|
|
||||||
|
|
||||||
HASH_FUNCTION = hashlib.sha384
|
HASH_FUNCTION = hashlib.sha384
|
||||||
DIGEST_LENGTH = HASH_FUNCTION().digest_size
|
DIGEST_LENGTH = HASH_FUNCTION().digest_size
|
||||||
|
@ -74,10 +76,10 @@ class CryptoUnavailableError(Exception):
|
||||||
|
|
||||||
|
|
||||||
def assert_crypto_availability(f):
|
def assert_crypto_availability(f):
|
||||||
"""Ensure Crypto module is available."""
|
"""Ensure cryptography module is available."""
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def wrapper(*args, **kwds):
|
def wrapper(*args, **kwds):
|
||||||
if AES is None:
|
if ciphers is None:
|
||||||
raise CryptoUnavailableError()
|
raise CryptoUnavailableError()
|
||||||
return f(*args, **kwds)
|
return f(*args, **kwds)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
@ -116,24 +118,39 @@ def encrypt_data(key, data):
|
||||||
Padding is n bytes of the value n, where 1 <= n <= blocksize.
|
Padding is n bytes of the value n, where 1 <= n <= blocksize.
|
||||||
"""
|
"""
|
||||||
iv = os.urandom(16)
|
iv = os.urandom(16)
|
||||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
cipher = ciphers.Cipher(
|
||||||
padding = 16 - len(data) % 16
|
algorithms.AES(key),
|
||||||
return iv + cipher.encrypt(data + six.int2byte(padding) * padding)
|
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):
|
def decrypt_data(key, data):
|
||||||
"""Decrypt the data with the given secret key."""
|
"""Decrypt the data with the given secret key."""
|
||||||
iv = data[:16]
|
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:
|
try:
|
||||||
result = cipher.decrypt(data[16:])
|
decryptor = cipher.decryptor()
|
||||||
|
result = decryptor.update(data[16:]) + decryptor.finalize()
|
||||||
except Exception:
|
except Exception:
|
||||||
raise DecryptError(_('Encrypted data appears to be corrupted.'))
|
raise DecryptError(_('Encrypted data appears to be corrupted.'))
|
||||||
|
|
||||||
# Strip the last n padding bytes where n is the last value in
|
# Strip the last n padding bytes where n is the last value in
|
||||||
# the plaintext
|
# 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):
|
def protect_data(keys, data):
|
||||||
|
|
|
@ -67,10 +67,10 @@ class MemcacheCryptPositiveTests(utils.BaseTestCase):
|
||||||
keys, protected[:-1])
|
keys, protected[:-1])
|
||||||
self.assertIsNone(memcache_crypt.unprotect_data(keys, None))
|
self.assertIsNone(memcache_crypt.unprotect_data(keys, None))
|
||||||
|
|
||||||
def test_no_pycrypt(self):
|
def test_no_cryptography(self):
|
||||||
aes = memcache_crypt.AES
|
aes = memcache_crypt.ciphers
|
||||||
memcache_crypt.AES = None
|
memcache_crypt.ciphers = None
|
||||||
self.assertRaises(memcache_crypt.CryptoUnavailableError,
|
self.assertRaises(memcache_crypt.CryptoUnavailableError,
|
||||||
memcache_crypt.encrypt_data, 'token', 'secret',
|
memcache_crypt.encrypt_data, 'token', 'secret',
|
||||||
'data')
|
'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
|
flake8-docstrings==0.2.1.post1 # MIT
|
||||||
|
|
||||||
coverage!=4.4,>=4.0 # Apache-2.0
|
coverage!=4.4,>=4.0 # Apache-2.0
|
||||||
|
cryptography>=1.6 # BSD/Apache-2.0
|
||||||
docutils>=0.11 # OSI-Approved Open Source, Public Domain
|
docutils>=0.11 # OSI-Approved Open Source, Public Domain
|
||||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||||
mock>=2.0 # BSD
|
mock>=2.0 # BSD
|
||||||
pycrypto>=2.6 # Public Domain
|
|
||||||
oslosphinx>=4.7.0 # Apache-2.0
|
oslosphinx>=4.7.0 # Apache-2.0
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
oslotest>=1.10.0 # Apache-2.0
|
||||||
reno>=1.8.0 # Apache-2.0
|
reno>=1.8.0 # Apache-2.0
|
||||||
|
|
Loading…
Reference in New Issue