summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2018-02-16 08:33:28 +0100
committerGitHub <noreply@github.com>2018-02-16 08:33:28 +0100
commitcd5db32c6c4e33b02b74f650aeadeaba9d31130d (patch)
treea67c5aa5eb6f3a81edeec41fc4c9678b7fc69ae3
parentb42880e3ead36d586638b4aeb0d8725a30dc865b (diff)
parenteed17d7cf8f24acce069725a0ee2ff740cf4e946 (diff)
downloadpysnmp-git-cd5db32c6c4e33b02b74f650aeadeaba9d31130d.tar.gz
Merge pull request #133 from mattsb42-aws/hybrid-crypto
Move to a hybrid crypto backend, using pyca/cryptography when available but failing back to PyCryptodomex for Python versions that pyca/cryptography does not support
-rw-r--r--.gitignore6
-rw-r--r--.travis.yml2
-rw-r--r--CHANGES.txt6
-rw-r--r--README.md4
-rw-r--r--pysnmp/crypto/__init__.py131
-rw-r--r--pysnmp/crypto/aes.py67
-rw-r--r--pysnmp/crypto/des.py74
-rw-r--r--pysnmp/crypto/des3.py67
-rw-r--r--pysnmp/proto/secmod/eso/priv/des3.py24
-rw-r--r--pysnmp/proto/secmod/rfc3414/priv/des.py22
-rw-r--r--pysnmp/proto/secmod/rfc3826/priv/aes.py23
-rw-r--r--requirements.txt7
-rw-r--r--setup.py20
-rw-r--r--tox.ini5
14 files changed, 391 insertions, 67 deletions
diff --git a/.gitignore b/.gitignore
index 77f208ce..7da35b06 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,9 @@ docs/source/.templates/layout.html
# Virtual envs
venv*
+
+# Tox
+.tox/
+
+# Pyenv
+.python-version
diff --git a/.travis.yml b/.travis.yml
index eaf5f481..56f9a710 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,4 +16,4 @@ install:
- pip install -e .
- pip install pysnmp-mibs
script:
- - sh runtests.sh
+ - travis_wait 20 sh runtests.sh
diff --git a/CHANGES.txt b/CHANGES.txt
index d81161a6..c55ddc16 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,10 @@
+Revision 4.?.?, released 2018-??-??
+-----------------------------------
+
+- Crypto abstraction layer added to allow use of pyca/cryptography instead of Pycryptodome
+- Dependencies modified to use pyca/cryptography for supported Python versions
+
Revision 4.4.4, released 2018-01-03
-----------------------------------
diff --git a/README.md b/README.md
index 9489ed1f..bb941b18 100644
--- a/README.md
+++ b/README.md
@@ -56,8 +56,10 @@ $ pip install pysnmp
to download and install PySNMP along with its dependencies:
* [PyASN1](http://snmplabs.com/pyasn1/)
-* [PyCryptodomex](https://pycryptodome.readthedocs.io) (required only if SNMPv3 encryption is in use)
* [PySMI](http://snmplabs.com/pysmi/) (required for MIB services only)
+* A supported cryptography backend (required only if SNMPv3 encryption is in use)
+ * [pyca/cryptography](http://cryptography.io/) for Python 2.7 and 3.4+
+ * [PyCryptodomex](https://pycryptodome.readthedocs.io) for Python 2.4-2.6 and 3.2-3.3
Besides the library, command-line [SNMP utilities](https://github.com/etingof/pysnmp-apps)
written in pure-Python could be installed via:
diff --git a/pysnmp/crypto/__init__.py b/pysnmp/crypto/__init__.py
new file mode 100644
index 00000000..2431cd91
--- /dev/null
+++ b/pysnmp/crypto/__init__.py
@@ -0,0 +1,131 @@
+"""Backend-selecting cryptographic logic to allow migration to pyca/cryptography
+without immediately dropping support for legacy minor Python versions.
+
+On installation, the correct backend dependency is selected based on the Python
+version. Versions that are supported by pyca/cryptography use that backend; all
+other versions (currently 2.4, 2.5, 2.6, 3.2, and 3.3) fall back to Pycryptodome.
+"""
+from pysnmp.proto import errind, error
+CRYPTOGRAPHY = 'cryptography'
+CRYPTODOME = 'Cryptodome'
+
+# Determine the available backend. Always prefer cryptography if it is available.
+try:
+ import cryptography
+ backend = CRYPTOGRAPHY
+except ImportError:
+ try:
+ import Cryptodome
+ backend = CRYPTODOME
+ except ImportError:
+ backend = None
+
+
+def _cryptodome_encrypt(cipher_factory, plaintext, key, iv):
+ """Use a Pycryptodome cipher factory to encrypt data.
+
+ :param cipher_factory: Factory callable that builds a Pycryptodome Cipher instance based
+ on the key and IV
+ :type cipher_factory: callable
+ :param bytes plaintext: Plaintext data to encrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Encrypted ciphertext
+ :rtype: bytes
+ """
+ encryptor = cipher_factory(key, iv)
+ return encryptor.encrypt(plaintext)
+
+
+def _cryptodome_decrypt(cipher_factory, ciphertext, key, iv):
+ """Use a Pycryptodome cipher factory to decrypt data.
+
+ :param cipher_factory: Factory callable that builds a Pycryptodome Cipher instance based
+ on the key and IV
+ :type cipher_factory: callable
+ :param bytes ciphertext: Ciphertext data to decrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Decrypted plaintext
+ :rtype: bytes
+ """
+ decryptor = cipher_factory(key, iv)
+ return decryptor.decrypt(ciphertext)
+
+
+def _cryptography_encrypt(cipher_factory, plaintext, key, iv):
+ """Use a cryptography cipher factory to encrypt data.
+
+ :param cipher_factory: Factory callable that builds a cryptography Cipher instance based
+ on the key and IV
+ :type cipher_factory: callable
+ :param bytes plaintext: Plaintext data to encrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Encrypted ciphertext
+ :rtype: bytes
+ """
+ encryptor = cipher_factory(key, iv).encryptor()
+ return encryptor.update(plaintext) + encryptor.finalize()
+
+
+def _cryptography_decrypt(cipher_factory, ciphertext, key, iv):
+ """Use a cryptography cipher factory to decrypt data.
+
+ :param cipher_factory: Factory callable that builds a cryptography Cipher instance based
+ on the key and IV
+ :type cipher_factory: callable
+ :param bytes ciphertext: Ciphertext data to decrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Decrypted plaintext
+ :rtype: bytes
+ """
+ decryptor = cipher_factory(key, iv).decryptor()
+ return decryptor.update(ciphertext) + decryptor.finalize()
+
+
+_DECRYPT_MAP = {
+ CRYPTOGRAPHY: _cryptography_decrypt,
+ CRYPTODOME: _cryptodome_decrypt
+}
+_ENCRYPT_MAP = {
+ CRYPTOGRAPHY: _cryptography_encrypt,
+ CRYPTODOME: _cryptodome_encrypt
+}
+
+
+def generic_encrypt(cipher_factory_map, plaintext, key, iv):
+ """Encrypt data using the available backend.
+
+ :param dict cipher_factory_map: Dictionary that maps the backend name to a cipher factory
+ callable for that backend
+ :param bytes plaintext: Plaintext data to encrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Encrypted ciphertext
+ :rtype: bytes
+ """
+ if backend is None:
+ raise error.StatusInformation(
+ errorIndication=errind.encryptionError
+ )
+ return _ENCRYPT_MAP[backend](cipher_factory_map[backend], plaintext, key, iv)
+
+
+def generic_decrypt(cipher_factory_map, ciphertext, key, iv):
+ """Decrypt data using the available backend.
+
+ :param dict cipher_factory_map: Dictionary that maps the backend name to a cipher factory
+ callable for that backend
+ :param bytes ciphertext: Ciphertext data to decrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Decrypted plaintext
+ :rtype: bytes
+ """
+ if backend is None:
+ raise error.StatusInformation(
+ errorIndication=errind.decryptionError
+ )
+ return _DECRYPT_MAP[backend](cipher_factory_map[backend], ciphertext, key, iv)
diff --git a/pysnmp/crypto/aes.py b/pysnmp/crypto/aes.py
new file mode 100644
index 00000000..20e91e7f
--- /dev/null
+++ b/pysnmp/crypto/aes.py
@@ -0,0 +1,67 @@
+"""
+Crypto logic for RFC3826.
+
+https://tools.ietf.org/html/rfc3826
+"""
+from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRAPHY, generic_decrypt, generic_encrypt
+
+if backend == CRYPTOGRAPHY:
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
+elif backend == CRYPTODOME:
+ from Cryptodome.Cipher import AES
+
+
+def _cryptodome_cipher(key, iv):
+ """Build a Pycryptodome AES Cipher object.
+
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: AES Cipher instance
+ """
+ return AES.new(key, AES.MODE_CFB, iv, segment_size=128)
+
+
+def _cryptography_cipher(key, iv):
+ """Build a cryptography AES Cipher object.
+
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: AES Cipher instance
+ :rtype: cryptography.hazmat.primitives.ciphers.Cipher
+ """
+ return Cipher(
+ algorithm=algorithms.AES(key),
+ mode=modes.CFB(iv),
+ backend=default_backend()
+ )
+
+
+_CIPHER_FACTORY_MAP = {
+ CRYPTOGRAPHY: _cryptography_cipher,
+ CRYPTODOME: _cryptodome_cipher
+}
+
+
+def encrypt(plaintext, key, iv):
+ """Encrypt data using AES on the available backend.
+
+ :param bytes plaintext: Plaintext data to encrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Encrypted ciphertext
+ :rtype: bytes
+ """
+ return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
+
+
+def decrypt(ciphertext, key, iv):
+ """Decrypt data using AES on the available backend.
+
+ :param bytes ciphertext: Ciphertext data to decrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Decrypted plaintext
+ :rtype: bytes
+ """
+ return generic_decrypt(_CIPHER_FACTORY_MAP, ciphertext, key, iv)
diff --git a/pysnmp/crypto/des.py b/pysnmp/crypto/des.py
new file mode 100644
index 00000000..12ce611b
--- /dev/null
+++ b/pysnmp/crypto/des.py
@@ -0,0 +1,74 @@
+"""
+Crypto logic for RFC3414.
+
+https://tools.ietf.org/html/rfc3414
+"""
+from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRAPHY, generic_decrypt, generic_encrypt
+
+if backend == CRYPTOGRAPHY:
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
+elif backend == CRYPTODOME:
+ from Cryptodome.Cipher import DES
+
+
+def _cryptodome_cipher(key, iv):
+ """Build a Pycryptodome DES Cipher object.
+
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: DES Cipher instance
+ """
+ return DES.new(key, DES.MODE_CBC, iv)
+
+
+def _cryptography_cipher(key, iv):
+ """Build a cryptography DES(-like) Cipher object.
+
+ .. note::
+
+ pyca/cryptography does not support DES directly because it is a seriously old, insecure,
+ and deprecated algorithm. However, triple DES is just three rounds of DES (encrypt,
+ decrypt, encrypt) done by taking a key three times the size of a DES key and breaking
+ it into three pieces. So triple DES with des_key * 3 is equivalent to DES.
+
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: TripleDES Cipher instance providing DES behavior by using provided DES key
+ :rtype: cryptography.hazmat.primitives.ciphers.Cipher
+ """
+ return Cipher(
+ algorithm=algorithms.TripleDES(key * 3),
+ mode=modes.CBC(iv),
+ backend=default_backend()
+ )
+
+
+_CIPHER_FACTORY_MAP = {
+ CRYPTOGRAPHY: _cryptography_cipher,
+ CRYPTODOME: _cryptodome_cipher
+}
+
+
+def encrypt(plaintext, key, iv):
+ """Encrypt data using DES on the available backend.
+
+ :param bytes plaintext: Plaintext data to encrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Encrypted ciphertext
+ :rtype: bytes
+ """
+ return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
+
+
+def decrypt(ciphertext, key, iv):
+ """Decrypt data using DES on the available backend.
+
+ :param bytes ciphertext: Ciphertext data to decrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Decrypted plaintext
+ :rtype: bytes
+ """
+ return generic_decrypt(_CIPHER_FACTORY_MAP, ciphertext, key, iv)
diff --git a/pysnmp/crypto/des3.py b/pysnmp/crypto/des3.py
new file mode 100644
index 00000000..1ccb9aeb
--- /dev/null
+++ b/pysnmp/crypto/des3.py
@@ -0,0 +1,67 @@
+"""
+Crypto logic for Reeder 3DES-EDE for USM (Internet draft).
+
+https://tools.ietf.org/html/draft-reeder-snmpv3-usm-3desede-00
+"""
+from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRAPHY, generic_decrypt, generic_encrypt
+
+if backend == CRYPTOGRAPHY:
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
+elif backend == CRYPTODOME:
+ from Cryptodome.Cipher import DES3
+
+
+def _cryptodome_cipher(key, iv):
+ """Build a Pycryptodome DES3 Cipher object.
+
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: DES3 Cipher instance
+ """
+ return DES3.new(key, DES3.MODE_CBC, iv)
+
+
+def _cryptography_cipher(key, iv):
+ """Build a cryptography TripleDES Cipher object.
+
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: TripleDES Cipher instance
+ :rtype: cryptography.hazmat.primitives.ciphers.Cipher
+ """
+ return Cipher(
+ algorithm=algorithms.TripleDES(key),
+ mode=modes.CBC(iv),
+ backend=default_backend()
+ )
+
+
+_CIPHER_FACTORY_MAP = {
+ CRYPTOGRAPHY: _cryptography_cipher,
+ CRYPTODOME: _cryptodome_cipher
+}
+
+
+def encrypt(plaintext, key, iv):
+ """Encrypt data using triple DES on the available backend.
+
+ :param bytes plaintext: Plaintext data to encrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Encrypted ciphertext
+ :rtype: bytes
+ """
+ return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
+
+
+def decrypt(ciphertext, key, iv):
+ """Decrypt data using triple DES on the available backend.
+
+ :param bytes ciphertext: Ciphertext data to decrypt
+ :param bytes key: Encryption key
+ :param bytes IV: Initialization vector
+ :returns: Decrypted plaintext
+ :rtype: bytes
+ """
+ return generic_decrypt(_CIPHER_FACTORY_MAP, ciphertext, key, iv)
diff --git a/pysnmp/proto/secmod/eso/priv/des3.py b/pysnmp/proto/secmod/eso/priv/des3.py
index 426df633..bf39a8ff 100644
--- a/pysnmp/proto/secmod/eso/priv/des3.py
+++ b/pysnmp/proto/secmod/eso/priv/des3.py
@@ -5,6 +5,7 @@
# License: http://snmplabs.com/pysnmp/license.html
#
import random
+from pysnmp.crypto import des3
from pysnmp.proto.secmod.rfc3414.priv import base
from pysnmp.proto.secmod.rfc3414.auth import hmacmd5, hmacsha
from pysnmp.proto.secmod.rfc3414 import localkey
@@ -12,7 +13,6 @@ from pysnmp.proto.secmod.rfc7860.auth import hmacsha2
from pysnmp.proto import errind, error
from pyasn1.type import univ
from pyasn1.compat.octets import null
-from math import ceil
try:
from hashlib import md5, sha1
@@ -23,11 +23,6 @@ except ImportError:
md5 = md5.new
sha1 = sha.new
-try:
- from Cryptodome.Cipher import DES3
-except ImportError:
- DES3 = None
-
random.seed()
@@ -113,32 +108,21 @@ class Des3(base.AbstractEncryptionService):
# 5.1.1.2
def encryptData(self, encryptKey, privParameters, dataToEncrypt):
- if DES3 is None:
- raise error.StatusInformation(
- errorIndication=errind.encryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
des3Key, salt, iv = self.__getEncryptionKey(
encryptKey, snmpEngineBoots
)
- des3Obj = DES3.new(des3Key, DES3.MODE_CBC, iv)
-
privParameters = univ.OctetString(salt)
plaintext = dataToEncrypt + univ.OctetString((0,) * (8 - len(dataToEncrypt) % 8)).asOctets()
- ciphertext = des3Obj.encrypt(plaintext)
+ ciphertext = des3.encrypt(plaintext, des3Key, iv)
return univ.OctetString(ciphertext), privParameters
# 5.1.1.3
def decryptData(self, decryptKey, privParameters, encryptedData):
- if DES3 is None:
- raise error.StatusInformation(
- errorIndication=errind.decryptionError
- )
snmpEngineBoots, snmpEngineTime, salt = privParameters
if len(salt) != 8:
@@ -153,9 +137,7 @@ class Des3(base.AbstractEncryptionService):
errorIndication=errind.decryptionError
)
- des3Obj = DES3.new(des3Key, DES3.MODE_CBC, iv)
-
ciphertext = encryptedData.asOctets()
- plaintext = des3Obj.decrypt(ciphertext)
+ plaintext = des3.decrypt(ciphertext, des3Key, iv)
return plaintext
diff --git a/pysnmp/proto/secmod/rfc3414/priv/des.py b/pysnmp/proto/secmod/rfc3414/priv/des.py
index b66889e2..b874162a 100644
--- a/pysnmp/proto/secmod/rfc3414/priv/des.py
+++ b/pysnmp/proto/secmod/rfc3414/priv/des.py
@@ -5,6 +5,7 @@
# License: http://snmplabs.com/pysnmp/license.html
#
import random
+from pysnmp.crypto import des
from pysnmp.proto.secmod.rfc3414.priv import base
from pysnmp.proto.secmod.rfc3414.auth import hmacmd5, hmacsha
from pysnmp.proto.secmod.rfc3414 import localkey
@@ -14,10 +15,6 @@ from pyasn1.type import univ
from sys import version_info
try:
- from Cryptodome.Cipher import DES
-except ImportError:
- DES = None
-try:
from hashlib import md5, sha1
except ImportError:
import md5
@@ -98,11 +95,6 @@ class Des(base.AbstractEncryptionService):
# 8.2.4.1
def encryptData(self, encryptKey, privParameters, dataToEncrypt):
- if DES is None:
- raise error.StatusInformation(
- errorIndication=errind.encryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
# 8.3.1.1
@@ -114,20 +106,14 @@ class Des(base.AbstractEncryptionService):
privParameters = univ.OctetString(salt)
# 8.1.1.2
- desObj = DES.new(desKey, DES.MODE_CBC, iv)
plaintext = dataToEncrypt + univ.OctetString((0,) * (8 - len(dataToEncrypt) % 8)).asOctets()
- ciphertext = desObj.encrypt(plaintext)
+ ciphertext = des.encrypt(plaintext, desKey, iv)
# 8.3.1.3 & 4
return univ.OctetString(ciphertext), privParameters
# 8.2.4.2
def decryptData(self, decryptKey, privParameters, encryptedData):
- if DES is None:
- raise error.StatusInformation(
- errorIndication=errind.decryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
# 8.3.2.1
@@ -147,7 +133,5 @@ class Des(base.AbstractEncryptionService):
errorIndication=errind.decryptionError
)
- desObj = DES.new(desKey, DES.MODE_CBC, iv)
-
# 8.3.2.6
- return desObj.decrypt(encryptedData.asOctets())
+ return des.decrypt(encryptedData.asOctets(), desKey, iv)
diff --git a/pysnmp/proto/secmod/rfc3826/priv/aes.py b/pysnmp/proto/secmod/rfc3826/priv/aes.py
index c702a418..82fa0da5 100644
--- a/pysnmp/proto/secmod/rfc3826/priv/aes.py
+++ b/pysnmp/proto/secmod/rfc3826/priv/aes.py
@@ -6,6 +6,7 @@
#
import random
from pyasn1.type import univ
+from pysnmp.crypto import aes
from pysnmp.proto.secmod.rfc3414.priv import base
from pysnmp.proto.secmod.rfc3414.auth import hmacmd5, hmacsha
from pysnmp.proto.secmod.rfc7860.auth import hmacsha2
@@ -13,10 +14,6 @@ from pysnmp.proto.secmod.rfc3414 import localkey
from pysnmp.proto import errind, error
try:
- from Cryptodome.Cipher import AES
-except ImportError:
- AES = None
-try:
from hashlib import md5, sha1
except ImportError:
import md5
@@ -102,11 +99,6 @@ class Aes(base.AbstractEncryptionService):
# 3.2.4.1
def encryptData(self, encryptKey, privParameters, dataToEncrypt):
- if AES is None:
- raise error.StatusInformation(
- errorIndication=errind.encryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
# 3.3.1.1
@@ -115,23 +107,16 @@ class Aes(base.AbstractEncryptionService):
)
# 3.3.1.3
- aesObj = AES.new(aesKey, AES.MODE_CFB, iv, segment_size=128)
-
# PyCrypto seems to require padding
dataToEncrypt = dataToEncrypt + univ.OctetString((0,) * (16 - len(dataToEncrypt) % 16)).asOctets()
- ciphertext = aesObj.encrypt(dataToEncrypt)
+ ciphertext = aes.encrypt(dataToEncrypt, aesKey, iv)
# 3.3.1.4
return univ.OctetString(ciphertext), univ.OctetString(salt)
# 3.2.4.2
def decryptData(self, decryptKey, privParameters, encryptedData):
- if AES is None:
- raise error.StatusInformation(
- errorIndication=errind.decryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
# 3.3.2.1
@@ -145,10 +130,8 @@ class Aes(base.AbstractEncryptionService):
decryptKey, snmpEngineBoots, snmpEngineTime, salt
)
- aesObj = AES.new(aesKey, AES.MODE_CFB, iv, segment_size=128)
-
# PyCrypto seems to require padding
encryptedData = encryptedData + univ.OctetString((0,) * (16 - len(encryptedData) % 16)).asOctets()
# 3.3.2.4-6
- return aesObj.decrypt(encryptedData.asOctets())
+ return aes.decrypt(encryptedData.asOctets(), aesKey, iv)
diff --git a/requirements.txt b/requirements.txt
index d90ebe40..76ab7775 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,8 @@
pysmi
-pycryptodomex
+pycryptodomex; python_version < '2.7'
+cryptography; python_version == '2.7'
+pycryptodomex; python_version == '3.2'
+pycryptodomex; python_version == '3.3'
+cryptography; python_version >= '3.4'
pyasn1>=0.2.3
+ordereddict; python_version < '2.7'
diff --git a/setup.py b/setup.py
index 0fac0fca..f5a81a78 100644
--- a/setup.py
+++ b/setup.py
@@ -50,15 +50,26 @@ def howto_install_setuptools():
""")
-if sys.version_info[:2] < (2, 4):
+py_version = sys.version_info[:2]
+if py_version < (2, 4):
print("ERROR: this package requires Python 2.4 or later!")
sys.exit(1)
+if py_version < (2, 7) or (py_version >= (3, 0) and py_version < (3, 4)):
+ crypto_lib = 'pycryptodomex'
+else:
+ crypto_lib = 'cryptography'
+
+requires = ['pyasn1>=0.2.3', 'pysmi', crypto_lib]
+
+if py_version < (2, 7):
+ requires.append('ordereddict')
+
try:
from setuptools import setup
params = {
- 'install_requires': ['pyasn1>=0.2.3', 'pysmi', 'pycryptodomex'],
+ 'install_requires': requires,
'zip_safe': True
}
@@ -71,8 +82,8 @@ except ImportError:
from distutils.core import setup
params = {}
- if sys.version_info[:2] > (2, 4):
- params['requires'] = ['pyasn1(>=0.2.3)', 'pysmi', 'pycryptodomex']
+ if py_version > (2, 4):
+ params['requires'] = requires
doclines = [x.strip() for x in (__doc__ or '').split('\n') if x]
@@ -101,6 +112,7 @@ params.update({
'pysnmp.carrier.twisted.dgram',
'pysnmp.carrier.asyncio',
'pysnmp.carrier.asyncio.dgram',
+ 'pysnmp.crypto',
'pysnmp.entity',
'pysnmp.entity.rfc3413',
'pysnmp.entity.rfc3413.oneliner',
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 00000000..af983437
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,5 @@
+[envlist]
+envlist = py{26,27,33,34,35,36}
+
+[testenv]
+commands = {toxinidir}/runtests.sh