Skip to content

Commit

Permalink
Update to switch from cryptodomex to cryptography
Browse files Browse the repository at this point in the history
- Drop any attempt to support Python 2.7
- Expect Python 3.6 or greater as need secrets module
  • Loading branch information
danizen committed Mar 29, 2020
1 parent bde8895 commit 31c7297
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 43 deletions.
8 changes: 1 addition & 7 deletions confsecrets/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,8 @@
from confsecrets.vault import Vault


try:
from Cryptodome import Random
except ImportError:
from Crypto import Random


def newsalt_command(raw=False):
newsalt = Random.new().read(8)
newsalt = os.urandom(8)
if raw:
print(newsalt)
else:
Expand Down
66 changes: 34 additions & 32 deletions confsecrets/pbe.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
"""
Wrap PyCrypto for safe and effective Password Based Encryption (PBE)
"""
from base64 import b64encode, b64decode
import os
import secrets
import string
from base64 import b64encode, b64decode
from enum import Enum
from six.moves import shlex_quote

try:
# cryptodomex
from Cryptodome import Random
from Cryptodome.Random import random
from Cryptodome.Hash.HMAC import HMAC
from Cryptodome.Hash import SHA256
from Cryptodome.Cipher import AES
from Cryptodome.Protocol.KDF import PBKDF2
except ImportError:
# cryptodome or PyCrypto
from Crypto import Random
from Crypto.Random import random
from Crypto.Hash.HMAC import HMAC
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from shlex import quote as shlex_quote

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, hmac
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

__all__ = [
'InvalidSalt',
Expand Down Expand Up @@ -165,8 +154,8 @@ def generate(cls, length=0):
def generate_candidate(length):
bpass = bytearray(length)
for i in range(length):
cclass = random.choice(required_classes)
bpass[i] = ord(random.choice(cclass.characters()))
cclass = secrets.choice(required_classes)
bpass[i] = ord(secrets.choice(cclass.characters()))
return bpass.decode('ascii')
# each candidate is very likely to meet the requirements, but let's be sure
while True:
Expand Down Expand Up @@ -211,9 +200,14 @@ def key(self):
Compute a derived key based on attributes on this object.
"""
if self.__key is None:
def proof(password, salt):
return HMAC(password, salt, SHA256).digest()
self.__key = PBKDF2(self.password, self.salt, self.key_size, self.iterations, prf=proof)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=self.key_size,
salt=self.salt,
iterations=self.iterations,
backend=default_backend()
)
self.__key = kdf.derive(self.password)
return self.__key

def encrypt_guts(self, cleartext):
Expand All @@ -225,9 +219,11 @@ def encrypt_guts(self, cleartext):
"""
if isinstance(cleartext, str):
cleartext = cleartext.encode('utf-8')
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CFB, iv)
ciphertext = cipher.encrypt(cleartext)
iv = os.urandom(16)
backend = default_backend()
cipher = Cipher(algorithms.AES(self.key), modes.CFB(iv), backend=backend)
encryptor = cipher.encryptor()
ciphertext = encryptor.update(cleartext) + encryptor.finalize()
return iv + ciphertext

def decrypt_guts(self, message):
Expand All @@ -239,8 +235,10 @@ def decrypt_guts(self, message):
"""
iv = message[0:16]
ciphertext = message[16:]
cipher = AES.new(self.key, AES.MODE_CFB, iv)
return cipher.decrypt(ciphertext)
backend = default_backend()
cipher = Cipher(algorithms.AES(self.key), modes.CFB(iv), backend=backend)
decryptor = cipher.decryptor()
return decryptor.update(ciphertext) + decryptor.finalize()

def encrypt(self, cleartext):
"""
Expand All @@ -250,7 +248,9 @@ def encrypt(self, cleartext):
:return: Base64 encoded encrypted bytes with a mac
"""
message_bytes = self.encrypt_guts(cleartext)
mac = HMAC(self.key, message_bytes, SHA256).digest()
h = hmac.HMAC(self.key, hashes.SHA256(), backend=default_backend())
h.update(message_bytes)
mac = h.finalize()
return b64encode(mac + message_bytes)

def decrypt_bytes(self, message):
Expand All @@ -263,8 +263,10 @@ def decrypt_bytes(self, message):
message_bytes = b64decode(message)
if len(message_bytes) < 48:
raise MessageTooShort()
expect_mac = message_bytes[0:32]
actual_mac = HMAC(self.key, message_bytes[32:], SHA256).digest()
expect_mac = message_bytes[:32]
h = hmac.HMAC(self.key, hashes.SHA256(), backend=default_backend())
h.update(message_bytes[32:])
actual_mac = h.finalize()
if actual_mac != expect_mac:
raise InvalidMessageAuthenticationCode()
return self.decrypt_guts(message_bytes[32:])
Expand Down
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# for cryptography
pycryptodomex
six
enum; python_version<"3"
cryptography

# for testing
pytest
pytest-django
pytest-cov
coverage
flake8
tox
1 change: 0 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
-r requirements.txt

tox
twine
mkdocs
ipython>=5.6.0

0 comments on commit 31c7297

Please sign in to comment.