Browse Source

Use cryptography instead of pycrypto

I was following up on [0] to see how much work would be required to
get Glance off pycrypto.  There's not much, so I decided to just do
it.  This patch rewrites the utility functions that were using the
pycrpyto library to instead use cryptography and removes pycrypto
from requirements.txt.

Note: PS1 included both the pycrypto- and cryptography- based
utilities in order to show that the new utilities can decrypt output
from the old utilities, and the old utilities can decrypt output from
the new utilities (needed for rolling upgrades).  You can look at the
earlier test results or download PS1 to run and verify.


Change-Id: Ib95644747e20bfd8ade5572d46651f9bd706e9da
Brian Rosmaita 5 years ago
  1. 32
  2. 1


@ -18,10 +18,13 @@ Routines for URL-safe encrypting/decrypting
import base64
import os
import random
from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Random import random
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers import modes
from oslo_utils import encodeutils
import six
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
@ -44,8 +47,10 @@ def urlsafe_encrypt(key, plaintext, blocksize=16):
Pads text to be encrypted
pad_length = (blocksize - len(text) % blocksize)
sr = random.StrongRandom()
pad = b''.join(six.int2byte(sr.randint(1, 0xFF))
# NOTE(rosmaita): I know this looks stupid, but we can't just
# use os.urandom() to get the bytes because we use char(0) as
# a delimiter
pad = b''.join(six.int2byte(random.SystemRandom().randint(1, 0xFF))
for i in range(pad_length - 1))
# We use chr(0) as a delimiter between text and padding
return text + b'\0' + pad
@ -53,9 +58,13 @@ def urlsafe_encrypt(key, plaintext, blocksize=16):
plaintext = encodeutils.to_utf8(plaintext)
key = encodeutils.to_utf8(key)
# random initial 16 bytes for CBC
init_vector = Random.get_random_bytes(16)
cypher =, AES.MODE_CBC, init_vector)
padded = cypher.encrypt(pad(six.binary_type(plaintext)))
init_vector = os.urandom(16)
backend = default_backend()
cypher = Cipher(algorithms.AES(key), modes.CBC(init_vector),
encryptor = cypher.encryptor()
padded = encryptor.update(
pad(six.binary_type(plaintext))) + encryptor.finalize()
encoded = base64.urlsafe_b64encode(init_vector + padded)
if six.PY3:
encoded = encoded.decode('ascii')
@ -76,8 +85,11 @@ def urlsafe_decrypt(key, ciphertext):
ciphertext = encodeutils.to_utf8(ciphertext)
key = encodeutils.to_utf8(key)
ciphertext = base64.urlsafe_b64decode(ciphertext)
cypher =, AES.MODE_CBC, ciphertext[:16])
padded = cypher.decrypt(ciphertext[16:])
backend = default_backend()
cypher = Cipher(algorithms.AES(key), modes.CBC(ciphertext[:16]),
decryptor = cypher.decryptor()
padded = decryptor.update(ciphertext[16:]) + decryptor.finalize()
text = padded[:padded.rfind(b'\0')]
if six.PY3:
text = text.decode('utf-8')


@ -15,7 +15,6 @@ sqlalchemy-migrate>=0.9.6 # Apache-2.0
sqlparse>=0.2.2 # BSD
alembic>=0.8.10 # MIT
httplib2>=0.7.5 # MIT
pycrypto>=2.6 # Public Domain
oslo.config>=3.22.0 # Apache-2.0
oslo.concurrency>=3.8.0 # Apache-2.0
oslo.context>=2.12.0 # Apache-2.0