python-pskc branch master updated. 0.5-22-g25cb2fc
[
Date Prev][
Date Next]
[
Thread Prev][
Thread Next]
python-pskc branch master updated. 0.5-22-g25cb2fc
- From: Commits of the python-pskc project <python-pskc-commits [at] lists.arthurdejong.org>
- To: python-pskc-commits [at] lists.arthurdejong.org
- Reply-to: python-pskc-users [at] lists.arthurdejong.org
- Subject: python-pskc branch master updated. 0.5-22-g25cb2fc
- Date: Mon, 9 Oct 2017 23:09:47 +0200 (CEST)
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-pskc".
The branch, master has been updated
via 25cb2fc2e55147ad6b7a4a8a0754d972448a13a9 (commit)
via 225e569d8c10be1bf747f585507869c6aaa72865 (commit)
via 5dff7d4b44751df5768b99d74cbd7a977edd3a17 (commit)
from 2c8a9b7b1b87a3cc07aac800d77db4652165d3e1 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
https://arthurdejong.org/git/python-pskc/commit/?id=25cb2fc2e55147ad6b7a4a8a0754d972448a13a9
commit 25cb2fc2e55147ad6b7a4a8a0754d972448a13a9
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Mon Oct 9 21:33:07 2017 +0200
Ignore missing docstring in __init__ in flake
diff --git a/setup.cfg b/setup.cfg
index 730bb60..0c042e0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -19,6 +19,6 @@ cover-min-percentage=100
all_files = 1
[flake8]
-ignore = D101,D102,D105,D202,D205,D209,D400,N806
+ignore = D101,D102,D105,D107,D202,D205,D209,D400,N806
max-complexity = 14
max-line-length = 78
https://arthurdejong.org/git/python-pskc/commit/?id=225e569d8c10be1bf747f585507869c6aaa72865
commit 225e569d8c10be1bf747f585507869c6aaa72865
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Sep 30 23:55:42 2017 +0200
Replace pycrypto with cryptography
The cryptography library is better supported.
This uses the functions from cryptography for AES and Triple DES
encryption, replaces the (un)padding functions that were previously
implemented in python-pskc with cryptography and uses PBKDF2
implementation from hashlib.
diff --git a/pskc/crypto/__init__.py b/pskc/crypto/__init__.py
index 356e498..ac96d67 100644
--- a/pskc/crypto/__init__.py
+++ b/pskc/crypto/__init__.py
@@ -1,7 +1,7 @@
# __init__.py - general crypto utility functions
# coding: utf-8
#
-# Copyright (C) 2016 Arthur de Jong
+# Copyright (C) 2017 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -19,20 +19,3 @@
# 02110-1301 USA
"""Implement crypto utility functions."""
-
-
-def pad(value, block_size):
- """Pad the value to block_size length."""
- padding = block_size - (len(value) % block_size)
- return value + padding * chr(padding).encode('ascii')
-
-
-def unpad(value, block_size):
- """Remove padding from the plaintext."""
- from pskc.exceptions import DecryptionError
- padding = ord(value[-1:])
- # only unpad if all padding bytes are the same
- if (padding > 0 and padding <= block_size and
- value[-padding:] == padding * chr(padding).encode('ascii')):
- return value[:-padding]
- raise DecryptionError('Invalid padding')
diff --git a/pskc/crypto/aeskw.py b/pskc/crypto/aeskw.py
index 7396f26..84e6f85 100644
--- a/pskc/crypto/aeskw.py
+++ b/pskc/crypto/aeskw.py
@@ -23,7 +23,8 @@
import binascii
import struct
-from Crypto.Cipher import AES
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from pskc.exceptions import DecryptionError, EncryptionError
@@ -71,19 +72,20 @@ def wrap(plaintext, key, iv=None, pad=None):
else:
iv = RFC3394_IV
- encrypt = AES.new(key).encrypt
+ cipher = Cipher(algorithms.AES(key), modes.ECB(), default_backend())
+ encryptor = cipher.encryptor()
n = len(plaintext) // 8
if n == 1:
# RFC 5649 shortcut
- return encrypt(iv + plaintext)
+ return encryptor.update(iv + plaintext)
A = iv
R = [plaintext[i * 8:i * 8 + 8]
for i in range(n)]
for j in range(6):
for i in range(n):
- A, R[i] = _split(encrypt(A + R[i]))
+ A, R[i] = _split(encryptor.update(A + R[i]))
A = _strxor(A, struct.pack('>Q', n * j + i + 1))
return A + b''.join(R)
@@ -103,11 +105,12 @@ def unwrap(ciphertext, key, iv=None, pad=None):
if len(ciphertext) % 8 != 0 or (pad is False and len(ciphertext) < 24):
raise DecryptionError('Ciphertext length wrong')
- decrypt = AES.new(key).decrypt
+ cipher = Cipher(algorithms.AES(key), modes.ECB(), default_backend())
+ decryptor = cipher.decryptor()
n = len(ciphertext) // 8 - 1
if n == 1:
- A, plaintext = _split(decrypt(ciphertext))
+ A, plaintext = _split(decryptor.update(ciphertext))
else:
A = ciphertext[:8]
R = [ciphertext[(i + 1) * 8:(i + 2) * 8]
@@ -115,7 +118,7 @@ def unwrap(ciphertext, key, iv=None, pad=None):
for j in reversed(range(6)):
for i in reversed(range(n)):
A = _strxor(A, struct.pack('>Q', n * j + i + 1))
- A, R[i] = _split(decrypt(A + R[i]))
+ A, R[i] = _split(decryptor.update(A + R[i]))
plaintext = b''.join(R)
if iv is None:
diff --git a/pskc/crypto/tripledeskw.py b/pskc/crypto/tripledeskw.py
index 9494026..0314fc9 100644
--- a/pskc/crypto/tripledeskw.py
+++ b/pskc/crypto/tripledeskw.py
@@ -21,17 +21,18 @@
"""Implement Triple DES key wrapping as described in RFC 3217."""
import binascii
+import hashlib
import os
-from Crypto.Cipher import DES3
-from Crypto.Hash import SHA
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from pskc.exceptions import DecryptionError, EncryptionError
def _cms_hash(value):
"""Return the key hash algorithm described in RFC 3217 section 2."""
- return SHA.new(value).digest()[:8]
+ return hashlib.sha1(value).digest()[:8]
RFC3217_IV = binascii.a2b_hex('4adda22c79e82105')
@@ -44,14 +45,19 @@ def wrap(plaintext, key, iv=None):
to wrap) using the provided key. If the iv is None, it is randomly
generated.
"""
- if len(plaintext) % DES3.block_size != 0:
+ if 8 * len(plaintext) % algorithms.TripleDES.block_size != 0:
raise EncryptionError('Plaintext length wrong')
if iv is None:
iv = os.urandom(8)
- cipher = DES3.new(key, DES3.MODE_CBC, iv)
- tmp = iv + cipher.encrypt(plaintext + _cms_hash(plaintext))
- cipher = DES3.new(key, DES3.MODE_CBC, RFC3217_IV)
- return cipher.encrypt(tmp[::-1])
+ backend = default_backend()
+ cipher = Cipher(algorithms.TripleDES(key), modes.CBC(iv), backend)
+ encryptor = cipher.encryptor()
+ tmp = (
+ iv + encryptor.update(plaintext + _cms_hash(plaintext)) +
+ encryptor.finalize())
+ cipher = Cipher(algorithms.TripleDES(key), modes.CBC(RFC3217_IV), backend)
+ encryptor = cipher.encryptor()
+ return encryptor.update(tmp[::-1]) + encryptor.finalize()
def unwrap(ciphertext, key):
@@ -60,12 +66,15 @@ def unwrap(ciphertext, key):
This uses the algorithm from RFC 3217 to decrypt the ciphertext (the
previously wrapped key) using the provided key.
"""
- if len(ciphertext) % DES3.block_size != 0:
+ if 8 * len(ciphertext) % algorithms.TripleDES.block_size != 0:
raise DecryptionError('Ciphertext length wrong')
- cipher = DES3.new(key, DES3.MODE_CBC, RFC3217_IV)
- tmp = cipher.decrypt(ciphertext)[::-1]
- cipher = DES3.new(key, DES3.MODE_CBC, tmp[:8])
- tmp = cipher.decrypt(tmp[8:])
+ backend = default_backend()
+ cipher = Cipher(algorithms.TripleDES(key), modes.CBC(RFC3217_IV), backend)
+ decryptor = cipher.decryptor()
+ tmp = (decryptor.update(ciphertext) + decryptor.finalize())[::-1]
+ cipher = Cipher(algorithms.TripleDES(key), modes.CBC(tmp[:8]), backend)
+ decryptor = cipher.decryptor()
+ tmp = decryptor.update(tmp[8:]) + decryptor.finalize()
if tmp[-8:] == _cms_hash(tmp[:-8]):
return tmp[:-8]
raise DecryptionError('CMS key checksum error')
diff --git a/pskc/encryption.py b/pskc/encryption.py
index 3ba22c3..d90476c 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -51,8 +51,30 @@ def algorithm_key_lengths(algorithm):
raise DecryptionError('Unsupported algorithm: %r' % algorithm)
+def _decrypt_cbc(algorithm, key, ciphertext, iv=None):
+ """Decrypt the ciphertext and return the plaintext value."""
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives import padding
+ from cryptography.hazmat.primitives.ciphers import Cipher, modes
+ from pskc.exceptions import DecryptionError
+ if not iv:
+ iv = ciphertext[:algorithm.block_size // 8]
+ ciphertext = ciphertext[algorithm.block_size // 8:]
+ cipher = Cipher(
+ algorithm(key), modes.CBC(iv), backend=default_backend())
+ decryptor = cipher.decryptor()
+ unpadder = padding.PKCS7(algorithm.block_size).unpadder()
+ try:
+ return unpadder.update(
+ decryptor.update(ciphertext) +
+ decryptor.finalize()) + unpadder.finalize()
+ except ValueError:
+ raise DecryptionError('Invalid padding')
+
+
def decrypt(algorithm, key, ciphertext, iv=None):
"""Decrypt the ciphertext and return the plaintext value."""
+ from cryptography.hazmat.primitives.ciphers import algorithms
from pskc.exceptions import DecryptionError
if key is None:
raise DecryptionError('No key available')
@@ -63,21 +85,9 @@ def decrypt(algorithm, key, ciphertext, iv=None):
if algorithm.endswith('#aes128-cbc') or \
algorithm.endswith('#aes192-cbc') or \
algorithm.endswith('#aes256-cbc'):
- from Crypto.Cipher import AES
- from pskc.crypto import unpad
- if not iv:
- iv = ciphertext[:AES.block_size]
- ciphertext = ciphertext[AES.block_size:]
- cipher = AES.new(key, AES.MODE_CBC, iv)
- return unpad(cipher.decrypt(ciphertext), AES.block_size)
+ return _decrypt_cbc(algorithms.AES, key, ciphertext, iv)
elif algorithm.endswith('#tripledes-cbc'):
- from Crypto.Cipher import DES3
- from pskc.crypto import unpad
- if not iv:
- iv = ciphertext[:DES3.block_size]
- ciphertext = ciphertext[DES3.block_size:]
- cipher = DES3.new(key, DES3.MODE_CBC, iv)
- return unpad(cipher.decrypt(ciphertext), DES3.block_size)
+ return _decrypt_cbc(algorithms.TripleDES, key, ciphertext, iv)
elif algorithm.endswith('#kw-aes128') or \
algorithm.endswith('#kw-aes192') or \
algorithm.endswith('#kw-aes256'):
@@ -89,8 +99,25 @@ def decrypt(algorithm, key, ciphertext, iv=None):
# no fallthrough because algorithm_key_lengths() fails with unknown algo
+def _encrypt_cbc(algorithm, key, plaintext, iv=None):
+ """Encrypt the provided value with the key using the algorithm."""
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives import padding
+ from cryptography.hazmat.primitives.ciphers import Cipher, modes
+ iv = iv or os.urandom(algorithm.block_size // 8)
+ cipher = Cipher(
+ algorithm(key), modes.CBC(iv), backend=default_backend())
+ encryptor = cipher.encryptor()
+ padder = padding.PKCS7(algorithm.block_size).padder()
+ return (
+ iv + encryptor.update(
+ padder.update(plaintext) + padder.finalize()) +
+ encryptor.finalize())
+
+
def encrypt(algorithm, key, plaintext, iv=None):
"""Encrypt the provided value with the key using the algorithm."""
+ from cryptography.hazmat.primitives.ciphers import algorithms
from pskc.exceptions import EncryptionError
if key is None:
raise EncryptionError('No key available')
@@ -101,17 +128,9 @@ def encrypt(algorithm, key, plaintext, iv=None):
if algorithm.endswith('#aes128-cbc') or \
algorithm.endswith('#aes192-cbc') or \
algorithm.endswith('#aes256-cbc'):
- from Crypto.Cipher import AES
- from pskc.crypto import pad
- iv = iv or os.urandom(AES.block_size)
- cipher = AES.new(key, AES.MODE_CBC, iv)
- return iv + cipher.encrypt(pad(plaintext, AES.block_size))
+ return _encrypt_cbc(algorithms.AES, key, plaintext, iv)
elif algorithm.endswith('#tripledes-cbc'):
- from Crypto.Cipher import DES3
- from pskc.crypto import pad
- iv = iv or os.urandom(DES3.block_size)
- cipher = DES3.new(key, DES3.MODE_CBC, iv)
- return iv + cipher.encrypt(pad(plaintext, DES3.block_size))
+ return _encrypt_cbc(algorithms.TripleDES, key, plaintext, iv)
elif algorithm.endswith('#kw-aes128') or \
algorithm.endswith('#kw-aes192') or \
algorithm.endswith('#kw-aes256'):
diff --git a/pskc/mac.py b/pskc/mac.py
index 5f258d0..65ec9b3 100644
--- a/pskc/mac.py
+++ b/pskc/mac.py
@@ -50,20 +50,14 @@ def _get_hash_obj(algorithm, *args):
raise DecryptionError('Unsupported MAC algorithm: %r' % algorithm)
-def get_mac_fn(algorithm):
- """Return a function that takes a key and a value and returns an HMAC for
- the specified algorithm."""
+def mac(algorithm, key, value):
+ """Generate the MAC value over the specified value."""
import hmac
- return lambda key, value: hmac.new(
+ return hmac.new(
key, value,
lambda *args: _get_hash_obj(algorithm, *args)).digest()
-def mac(algorithm, key, value):
- """Generate the MAC value over the specified value."""
- return get_mac_fn(algorithm)(key, value)
-
-
def mac_key_length(algorithm):
"""Recommended minimal key length in bytes for the set algorithm."""
# https://tools.ietf.org/html/rfc2104#section-3
diff --git a/setup.cfg b/setup.cfg
index 9f121ef..730bb60 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,5 +20,5 @@ all_files = 1
[flake8]
ignore = D101,D102,D105,D202,D205,D209,D400,N806
-max-complexity = 12
+max-complexity = 14
max-line-length = 78
diff --git a/setup.py b/setup.py
index 7670ec8..2472d7f 100755
--- a/setup.py
+++ b/setup.py
@@ -62,7 +62,7 @@ setup(
'Topic :: Text Processing :: Markup :: XML',
],
packages=find_packages(),
- install_requires=['pycrypto', 'python-dateutil'],
+ install_requires=['cryptography', 'python-dateutil'],
extras_require={
'lxml': ['lxml'],
'defuse': ['defusedxml'],
diff --git a/tests/test_crypto.doctest b/tests/test_crypto.doctest
deleted file mode 100644
index 73cef48..0000000
--- a/tests/test_crypto.doctest
+++ /dev/null
@@ -1,76 +0,0 @@
-test_crypto.doctest - test various internal crypto helper functions
-
-Copyright (C) 2016 Arthur de Jong
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Lesser General Public
-License as published by the Free Software Foundation; either
-version 2.1 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public
-License along with this library; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301 USA
-
->>> import random
->>> from binascii import a2b_hex, b2a_hex
->>> def tostr(x):
-... return str(x.decode())
->>> def decode(f):
-... return lambda x: tostr(f(x))
->>> b2a_hex = decode(b2a_hex)
-
->>> from pskc.crypto import pad, unpad
-
-
-Tests for padding and unpadding. This should follow PKCS#7 as defined in RFC
-5652 section 6.3.
-
->>> b2a_hex(pad(a2b_hex('1234'), 16))
-'12340e0e0e0e0e0e0e0e0e0e0e0e0e0e'
->>> b2a_hex(pad(a2b_hex('abcdefabcdefabcdefabcdefabcdef'), 16))
-'abcdefabcdefabcdefabcdefabcdef01'
->>> b2a_hex(pad(bytearray(16 * [65]), 16))
-'4141414141414141414141414141414110101010101010101010101010101010'
-
->>> b2a_hex(unpad(a2b_hex('12340e0e0e0e0e0e0e0e0e0e0e0e0e0e'), 16))
-'1234'
->>> b2a_hex(unpad(a2b_hex('abcdefabcdefabcdefabcdefabcdef01'), 16))
-'abcdefabcdefabcdefabcdefabcdef'
-
-
-Padding should always work but there are some conditions where unpadding does
-not work. Specifically, the to be removed padding should all have the same
-bytes:
-
->>> b2a_hex(unpad(a2b_hex('ffff0606060f0606'), 8)) # doctest:
+IGNORE_EXCEPTION_DETAIL
-Traceback (most recent call last):
- ...
-DecryptionError: Invalid padding
-
-The unpadding should also refuse to remove more than a block size worth of
-data:
-
->>> b2a_hex(unpad(a2b_hex('ffff0e0e0e0e0e0e0e0e0e0e0e0e0e0e'), 8)) # doctest:
+IGNORE_EXCEPTION_DETAIL
-Traceback (most recent call last):
- ...
-DecryptionError: Invalid padding
-
-
-We generate a number of (non-cryptographically strong) random strings and
-ensure that padding and unpadding works for them.
-
->>> for i in range(100):
-... l = random.randrange(1, 255)
-... r = bytearray(random.getrandbits(8) for x in range(l))
-... b = random.randrange(10, 100)
-... p = pad(r, b)
-... u = unpad(p, b)
-... if u != r:
-... raise ValueError('%s -> %s -> %s (%d)' % (
-... b2a_hex(r), b2a_hex(p), b2a_hex(u), b))
diff --git a/tests/test_encryption.doctest b/tests/test_encryption.doctest
index fa567ef..7e1d443 100644
--- a/tests/test_encryption.doctest
+++ b/tests/test_encryption.doctest
@@ -1,6 +1,6 @@
test_encryption.doctest - test various encryption schemes
-Copyright (C) 2014-2016 Arthur de Jong
+Copyright (C) 2014-2017 Arthur de Jong
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@@ -77,6 +77,11 @@ DecryptionError: Invalid key length
Traceback (most recent call last):
...
DecryptionError: Invalid key length
+>>> pskc.encryption.key = a2b_hex('11111111111111111111111111111111')
+>>> pskc.keys[0].secret # doctest: +IGNORE_EXCEPTION_DETAIL
+Traceback (most recent call last):
+ ...
+DecryptionError: Invalid padding
>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
>>> tostr(pskc.keys[0].secret)
'12345678901234567890'
diff --git a/tox.ini b/tox.ini
index fd591cf..731fcc6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -5,8 +5,6 @@ skip_missing_interpreters = True
[testenv]
deps = nose
coverage
- pycrypto
- python-dateutil
lxml: lxml
defusedxml: defusedxml
commands = nosetests
https://arthurdejong.org/git/python-pskc/commit/?id=5dff7d4b44751df5768b99d74cbd7a977edd3a17
commit 5dff7d4b44751df5768b99d74cbd7a977edd3a17
Author: Arthur de Jong <arthur@arthurdejong.org>
Date: Sat Sep 30 21:31:48 2017 +0200
Use PBKDF2 from hashlib
This uses pbkdf2_hmac() from hashlib for the PBKDF2 calculation.
The downside of this is that this function is only available since
Python 2.7.8.
diff --git a/pskc/encryption.py b/pskc/encryption.py
index 5ae31db..3ba22c3 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -28,6 +28,7 @@ The encryption key can be derived using the KeyDerivation
class.
import os
+import re
def algorithm_key_lengths(algorithm):
@@ -165,20 +166,28 @@ class KeyDerivation(object):
self._pbkdf2_prf = normalise_algorithm(value)
def derive_pbkdf2(self, password):
- from Crypto.Protocol.KDF import PBKDF2
- from pskc.mac import get_mac_fn
- from pskc.exceptions import DecryptionError, KeyDerivationError
- prf = None
+ from hashlib import pbkdf2_hmac
+ from pskc.exceptions import KeyDerivationError
+ prf = 'sha1'
if self.pbkdf2_prf:
- prf = get_mac_fn(self.pbkdf2_prf)
+ match = re.search(
+ r'^(.*#)?hmac-(?P<hash>[a-z0-9-]+)$', self.pbkdf2_prf)
+ if match:
+ prf = match.group('hash')
+ else:
+ raise KeyDerivationError(
+ 'Unsupported PRF: %r' % self.pbkdf2_prf)
if not all((password, self.pbkdf2_salt, self.pbkdf2_key_length,
self.pbkdf2_iterations)):
raise KeyDerivationError('Incomplete PBKDF2 configuration')
+ # force conversion to bytestring on Python 3
+ if not isinstance(password, type(b'')):
+ password = password.encode() # pragma: no cover (Py3 specific)
try:
- return PBKDF2(
- password, self.pbkdf2_salt, dkLen=self.pbkdf2_key_length,
- count=self.pbkdf2_iterations, prf=prf)
- except DecryptionError:
+ return pbkdf2_hmac(
+ prf, password, self.pbkdf2_salt, self.pbkdf2_iterations,
+ self.pbkdf2_key_length)
+ except ValueError:
raise KeyDerivationError(
'Pseudorandom function unsupported: %r' % self.pbkdf2_prf)
-----------------------------------------------------------------------
Summary of changes:
pskc/crypto/__init__.py | 19 +--------
pskc/crypto/aeskw.py | 17 ++++----
pskc/crypto/tripledeskw.py | 35 ++++++++++------
pskc/encryption.py | 94 ++++++++++++++++++++++++++++---------------
pskc/mac.py | 12 ++----
setup.cfg | 4 +-
setup.py | 2 +-
tests/test_crypto.doctest | 76 ----------------------------------
tests/test_encryption.doctest | 7 +++-
tox.ini | 2 -
10 files changed, 106 insertions(+), 162 deletions(-)
delete mode 100644 tests/test_crypto.doctest
hooks/post-receive
--
python-pskc
--
To unsubscribe send an email to
python-pskc-commits-unsubscribe@lists.arthurdejong.org or see
https://lists.arthurdejong.org/python-pskc-commits/
- python-pskc branch master updated. 0.5-22-g25cb2fc,
Commits of the python-pskc project